既然是一個小程序引發的思考,那么我們就先看看這個小程序,看看他有何神奇之處:
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,引用類型,它的值被方法的行為改變了。
親愛的童鞋們,你們明白了嗎?別看一點小程序,原來深挖可以得出那么多信息。其實也側面說明了基礎的重要性,童鞋們加油吧!
文章列表