1. 程式人生 > 其它 >一個小程式引發的思考

一個小程式引發的思考

   既然是一個小程式引發的思考,那麼我們就先看看這個小程式,看看他有何神奇之處:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass s = new MyClass();
            s.val = 10;
            int i = 20;
            Console.WriteLine("s.val={0},i={1}",s.val,i);
            MyMethod(s, i);
            Console.WriteLine("s.val={0},i={1}", s.val, i);
            Console.Read();
        }
        static void MyMethod(MyClass f1,int f2)
        {
            f1.val = f1.val + 5;
            f2 = f2 + 5;
        }
    }
    class MyClass
    {
        public int val = 20;
    }
}

呵呵,大家看了以後可能會感覺這不是很簡單的程式碼嗎?有什麼特別的地方嗎?沒有,真沒有!但是我想問下大家這兩次輸出結果會有什麼不同嗎?分別是什麼?有沒有得出兩次結果都是一樣的?這個程式輸出的結果是:

    可能有些童鞋可能會問,不應該是一樣的嗎?為什麼一個值變了,另外一個沒有變呢?這是為什麼呢?仔細的同學可能會發現static void MyMethod(MyClass f1,int f2)這個方法兩個引數的型別不一樣,f1屬於引用型別,f2屬於型別,是不是因為這個原因才導致兩個變數經過同樣的處理,s.val的值改變了,i的值卻沒有變。首先我們瞭解下什麼是值型別,什麼是引用型別。

值型別與引用型別(這個面試的時候經常會被問到)

    值型別:值型別只需要一段單獨的記憶體,用於儲存實際的資料,他存在棧裡面

    引用型別:引用型別需要兩段記憶體。

  • 第一段儲存實際的資料,它總是位於堆中。
  • 第二段是一個引用指向資料在堆中的位置,它通常位於棧中。

那這樣說,像上面s物件,它是一個引用型別,那它應該存放在堆中,但是val又是個值型別,那它不是應該存放在棧中嗎?

    請記住,對於一個引用型別,其例項部分始終存放在堆裡。既然val是物件s的一部分,那麼它們都會被存放在堆裡,無論它們是值型別還是引用型別。

這裡順便介紹下棧和堆

棧:棧是一個記憶體陣列,是一個LIFO(last-in first- out,後進先出)的資料結構,棧儲存幾種型別的資料。

  • 某些型別變數的值
  • 程式當前的執行環境
  • 傳遞給方法的引數 棧有以下幾個特徵:
  • 資料只能從棧的頂端插入和刪除。                                                                       
  • 把資料放到棧頂叫入棧(push)。
  • 從棧頂刪除資料叫出棧(pop) 堆:堆是一塊記憶體區域,在堆裡可以分配大塊的記憶體用於儲存某些的型別的物件。與棧不同,堆裡的記憶體能夠任意順序存入或移除。    雖然可以在堆裡儲存資料,但並不能顯式地刪除它們。CLR的自動GC在判斷出程式的程式碼不會再訪問某些資料時,自動清除無主的堆物件。我們因此可以不用操心這項使用其它程式語言時非常容易出錯的工作了。       在介紹了值型別、引用型別與堆和棧,那我們解析下上面程式的執行步驟:
    • 在方法被調之前,用作實參的變數s已經在棧裡了。
    • 隨著方法的開始,系統在棧中為形參分配空間,並從實參複製值。
    • 因為s是引用型別所以引用被複制,結果實參和形參都引用堆中的同一個物件。
    • 因為i是值型別,所以值被複制,產生了一個獨立的資料項。
    • 在方法的結尾,f2和物件f1的欄位都被加上5.
    • 方法執行後,形參被從棧中彈出。
    • i,值型別,它的值不受方法行為的影響。
    • s,引用型別,它的值被方法的行為改變了。

    親愛的童鞋們,你們明白了嗎?別看一點小程式,原來深挖可以得出那麼多資訊。其實也側面說明了基礎的重要性,童鞋們加油吧!