淺談值型別和引用型別在堆和棧中的儲存一
首先,讓我們來簡單瞭解一下什麼是“棧”(stack),什麼是“堆”(heap)。“棧”其實就是一種後入先出(LIFO)的資料結構。在我們.NET Framework裡面,由CLR負責管理,我們程式設計師不用去擔心垃圾回收的問題;每一個執行緒都有自己的專屬的“棧”。“堆”的存放就要零散一些,並且由 Garbage Collector(GC)執行管理,我們關注的垃圾回收部分,就是在“堆”上的垃圾回收;其次就是整個程序共用一個“堆”。
我們先來記住兩條黃金法則:
1.引用型別總是被分配到“堆”上。
2.值型別總是分配到它宣告的地方:
a.作為引用型別的成員變數分配到“堆”上
b.作為方法的區域性變數時分配到“棧”上
要真正理解上面的兩條黃金法則,還需要了解一下,“棧”和“堆”是如何工作的。首先以下面的這個方法為例:
public int AddFive(int pValue){ int result; result = pValue + 5; return result;}
1.方法AddFive()被壓入“棧”
2.緊接著方法引數pValue被壓入“棧”
3.然後是需要為result變數分配空間,這時被分配到“棧”上。
4.最後返回結果
通過將棧指標指向 AddFive()方法曾使用的可用的記憶體地址,所有在“棧”上的該方法所使用記憶體都被清空,且程式將自動回到“棧“上最初的方法呼叫的位置。
下面我們來看看,值型別的變數分配到“堆”上到情況。
public class MyInt{ public int MyValue;}public MyInt AddFive(int pValue){ var result = new MyInt(); result.MyValue = pValue + 5; return result;}
和前面一樣,執行緒開始執行函式,函式引數被壓入執行緒堆疊。
由於 MyInt 為引用型別,它被分配在“堆”上,並且由一個位於“棧”上的指標引用。
AddFive()函式執行完畢後,“棧”同樣會被清空。
最後,只剩下一個 MyInt 類被留在“堆”上(“棧”上再也沒有指向這個 MyInt 類的指標)!這個時候就需要GC機制來處理了。
好了,大家對“棧”和“堆”有一定的瞭解之後,下面讓我們來稍微深入一點,來看兩個例子。
public int ReturnValue(){ int x = 3; int y = x; y = 4; return x;}
返回值大家其實已經知道了吧,還是3。這是為什麼呢?這是因為,值型別,使用的就是值本身,其實在做int y = x的時候,就把3做了一份“拷貝”賦值給了y,然後在y = 4的時候,操作的是這個“拷貝”,並不會操作原來的3。
接下來我們看另外一個例子,MyInt還是使用上面例子中使用過的MyInt類。
public int ReturnValue(){ var x = new MyInt(); x.MyValue = 3; MyInt y; y = x; y.MyValue = 4; return x.MyValue;}
在這個程式碼中,x.MyValue是否還是會是3呢?是否y.MyValue還是操作的3的“拷貝”呢?其實只要執行一下這個程式碼,就可以得出結果,x.MyValue還是被修改成了4。這是因為,當我們使用引用型別時,我們實際上是在使用指向這些物件的地址,而不是直接使用這些物件本身。
好了,暫時就介紹到這裡,如果有什麼疑問,或者有誤的地方,歡迎大家指點和交流