C#將數據類型分爲兩種:值類型和引用類型。
這兩種類型存儲在內存的不同的地方:值類型存儲在堆棧(Stack)中,而引用類型則存儲在託管堆(managed)上。區分類型是值類型還是引用類型非常重要,這會造成不同的結果。在瞭解上面的知識之前首先應該弄清楚兩個問題。
1.什麼是棧?
棧可以理解爲一種內存結構,它是先進後出的,就像桶裝薯片一樣,放進去的時候是一個接一個壓進去的,在這裏我們成爲壓棧,而要去拿的時候都是從最後一個進去的開始那。這就是它的特點:先進後出。
在語言中比如我們的程序入口Main方法中有一個GetMoney的方法。它的執行順序就是先將Main方法壓棧再將GetMoney方法壓棧,那麼執行的時候就是先執行GetMoney方法,執行完畢之後再執行Main方法,這也是棧的應用。
A:棧只能在一端進行操作,也就是棧頂,因爲棧底是封閉的。
B:棧不需要開發人員進行管理內存,壓棧的時候自行分配內存,出棧的時候自行清理內存。
C:棧的可用空間不大,因此它的執行效率很高,它不能動態請求內存,只能夠爲內存大小確定的數據分配內存。
2.什麼是堆?
A:堆在C#中用於存儲實例對象,對象可能會有很多數據,因此它的第一個特性就是:容量大能夠存儲很多數據,而且能夠動態的分配存儲空間。
B:棧我們已經知道了只能在棧頂一端進行操作,但堆就不一樣了,它可以隨意的存取,非常靈活。
C:事情都是有雙面性的,堆的內存空間既然大能夠存儲的數據多,那麼它的執行效率肯定是沒有堆高的。
一:那麼值類型和引用類型到底是怎麼樣在內存中分配的?
A:對於值類型來說:其變量對應的值是存放在棧中。如果該值類型的實例作爲類型的成員,而該實例作爲引用類型的一部分的時候,則它被創建在GC堆上。
B:對於引用類型來說:一塊空間被分配在堆上,存儲應用本身的數據,而另外一塊則被分配在棧上,存儲對 堆上數據的引用(其實就是內存地址 也叫做指針)例如Person p = new Person();這裏可以分爲兩部分來理解。Person p相當於定義了對象的引用,也就是記錄了對象實例的指針,而不是對象本身。這個引用存儲在棧中,當沒有使用p = new Person()的時候,引用本身爲空也就是相當於指針沒有指向任何位置;當p = new Person()後,才根據真正的對象的大小動態的在堆中分配空間給對象實例,然後會將實例的引用賦值給p。到這裏纔算是完成了一個對象的實例化。如下圖:
在C#中還有一種概念叫按值傳遞和按引用傳遞?
1.什麼時按值傳遞?我們先來看兩段代碼。
public struct GameStateStruct
{
public int a { get ; set ; }
public int b { get ; set ; }
}
public class GameStateClass
{
public int a { get ; set ; }
public int b { get ; set ; }
}
1.定義了一個結構體 名稱爲GameStateStruct
2.定義了一個類 名稱爲GameStateClass
3.在Main方法中操作如下
var tempStract1 = new GameStateStruct ( ) ;
tempStract1. a = 1 ;
tempStract1. b = 1 ;
var tempStract2 = tempStract1;
tempStract1. a = 2 ;
tempStract1. b = 2 ;
Console. WriteLine ( tempStract1. a + " :" + tempStract1. b) ;
Console. WriteLine ( tempStract2. a + " :" + tempStract2. b) ;
4.結果如下:2:2 1:1 那就說明了:對於值類型而言會在棧上重新開闢一塊新的空間,將值複製過去。tempStract1和tempStract2是相互獨立的,在上述中改變了tempStract1的值並不會影響到tempStract2.
2 什麼是引用傳遞?
var tempClass1 = new GameStateClass ( ) ;
tempClass1. a = 1 ;
tempClass1. b = 1 ;
var tempClass2 = tempClass1;
tempClass1. a = 2 ;
tempClass1. b = 2 ;
Console. WriteLine ( tempClass1. a + " :" + tempClass1. b) ;
Console. WriteLine ( tempClass2. a + " :" + tempClass2. b) ;
結果如下:2:2 2:2 那就說明了:對於引用類型而言也會在棧上重新開闢一塊新的空間,但注意這裏和值傳遞的不同的是:這裏會將棧上的引用複製到新開闢的空間,引用,引用,引用!爲什麼可以這麼說呢?因爲從打印結果來看我們只是改變了tempClass1的a和b的值,並沒有去改變tempClass2的a和b的值,但是結果卻是一樣的。說明了改變tempClass1的值會對tempClass2的值產生影響。如下圖所示:
從上圖就可以很好的解釋爲什麼。當我們執行完var tempClass1 = new GameStateClass();時候如圖中黑線所示,在棧中會開闢一個空間存tempClass1對 堆上new的GameStateClass對象的引用(也可以說爲指針)。然後當tempClass2 = tempClass1執行的時候會在棧中重新開闢一個空間存tempClass2然後將tempClass1的引用賦值給他,也就是說tempClass2存儲的也是指向堆的new的GameStateClass的對象。爲了形象化這裏的箭頭可以看做引用。最後當我們通過tempClass1去修改對象的數據之後,因爲tempClass1和tempClass2都是指向同一個對象,所以我們通過tempClass2去訪問對象的屬性a和b的時候,結果也爲一樣的。這就驗證了上面的打印結果爲什麼時2:2 2:2了。
綜上所述:值傳遞和引用傳遞的區別就是:值類型是複製數據本身,形成相互獨立的數據存儲區,引用類型是複製引用(指針),存儲的是引用,引用指向同一對象。