大家都知道要學(xué)好 .NET,深入了解值類型和引用類型是必不可少的。在這里我給大家簡單分析一下它們內(nèi)存分配的區(qū)別和聯(lián)系。
在分析之前,我們先行構(gòu)造出一個(gè)最簡單的類引用類型:
局部變量的聲明
在我們使用類型時(shí),代碼里面必然少不了變量的聲明,我們先看一下方法內(nèi)的局部變量的聲明,請看如下代碼:
當(dāng)一個(gè)局部變量聲明之后,就會(huì)在棧的內(nèi)存中分配一塊內(nèi)存給這個(gè)變量,至于這塊內(nèi)存多大,里面存放什么東西,就要看這個(gè)變量是值類型還是引用類型了。
l?值類型
如果是值類型,為變量分配這塊內(nèi)存的大小就是值類型定義的大小,存放值類型自身的值(內(nèi)容)。比如,對于上面的整型變量 i,這塊內(nèi)存的大小就是 4個(gè)字節(jié)(一個(gè) int型定義的大小),如果執(zhí)行 i = 5;這行代碼,則這塊內(nèi)存的內(nèi)容就是 5(如圖 -1)。
對于任何值類型,無論是讀取還是寫入操作,可以一步到位,因?yàn)橹殿愋妥兞勘旧硭嫉膬?nèi)存就存放著值。
- 引用類型
如果是引用類型,為變量分配的這塊內(nèi)存的大小,就是一個(gè)內(nèi)存指針(實(shí)例引用、對象引用)的大小(在 32位系統(tǒng)上為 4字節(jié),在 64位系統(tǒng)上為 8字節(jié))。因?yàn)樗幸妙愋偷膶?shí)例(對象、值)都是創(chuàng)建在堆上的,而這個(gè)為變量分配的內(nèi)存就存放變量對應(yīng)在堆上的實(shí)例(對象、值)的內(nèi)存首地址(內(nèi)存指針),也叫實(shí)例(對象)的引用。以圖形化的方式展現(xiàn)仿佛是變量有一條線指向著它在堆中的實(shí)例(有如圖 -2),而如果變量的類型還沒有被實(shí)例化,則為零地址( null、空引用)。
以下為執(zhí)行 mc = new MyClass ();代碼后,內(nèi)存中的示例:
由圖 -2可知,變量 mc中存放的是 MyClass實(shí)例(對象)的對象引用,如果需要訪問 mc實(shí)例,系統(tǒng)需要首先從 mc變量中得到實(shí)例的引用(在堆中的地址),然后用這個(gè)引用(地址)找到堆中的實(shí)例,再進(jìn)行訪問。需要至少 2步操作才可以完成實(shí)例訪問。
類型賦值
另一個(gè)常見的操作就是類型的賦值操作,即變量之間的賦值。由于值類型和引用類型的變量內(nèi)部存放的內(nèi)容不同,導(dǎo)致在變量賦值的時(shí)候,會(huì)有相同的行為而有不同的結(jié)果。
- 值類型
請看如下代碼:
相信大家一定都知道最后的結(jié)果是 i:5, j:10。不過在 .NET中, int類型也是一個(gè)結(jié)構(gòu),不但可以存放整數(shù)值,還有一系列的方法和屬性可以使用,而非我們以前學(xué) C語言時(shí)的那種單純 int存放一個(gè)整數(shù)的概念。所以我們現(xiàn)在看針對 int的代碼,其實(shí)也是在看針對 struct類型的代碼。
對于值類型的賦值語句“ j = i”,請看圖 -3:
在執(zhí)行 j = i;語句時(shí),變量 i中的內(nèi)容被復(fù)制了一份,然后放到了變量 j中,此時(shí)變量 i和 j都有一個(gè)值為 5,同時(shí)也可以看出, i和 j的值現(xiàn)在互不相干,完全獨(dú)立,所以任意修改其中的某個(gè)變量的值,不會(huì)影響到另外一個(gè)。
- 引用類型
請看如下代碼:
代碼中先對 x進(jìn)行了實(shí)例化,然后將 x賦值到 y,這段代碼的結(jié)果請看圖 -4:
當(dāng)執(zhí)行 y = x;代碼時(shí),變量 x中的內(nèi)容同樣復(fù)制了一份,然后放到了變量 y之中,但是因?yàn)樽兞?x中存放是一個(gè)類型實(shí)例(對象)的引用,因此這次賦值操作等同于把這個(gè)引用傳遞給了變量 y,結(jié)果就是 x和 y中的引用指向堆中同一個(gè)類型的實(shí)例(對象)。
你可以使用 x的引用去修改 MyClass實(shí)例(對象),然后用 y的引用得到修改后的 MyClass實(shí)例(對象),反之亦可,因?yàn)?x和 y引用的是同一個(gè)實(shí)例(對象)。
復(fù)雜類型的內(nèi)存布局概述
以上內(nèi)容是以值類型或者引用類型為一個(gè)整體敘述值類型和引用類型的變量聲明和賦值的情況。下面我們看看值類型和引用類型內(nèi)部含有其他類型成員變量(一般稱為字段)的情況。雖然看起來情況似乎復(fù)雜了一點(diǎn),但是只要我們可以把握住值類型的值存放在值類型變量內(nèi)部,而引用類型的值在堆中存放,引用類型的變量只存放對它實(shí)例(對象)的引用這個(gè)原則,就可以很清晰的做出分析。
- 值類型
且看下面的類型定義代碼:
public struct MyStruct { /* 注意:作為結(jié)構(gòu),內(nèi)部字段是不能象下面所寫那樣,在聲明時(shí)直接初始化的。 * 但這里為了節(jié)省篇幅,從表達(dá)語義的角度,直接在聲明時(shí)初始化了 * 此結(jié)構(gòu)的代碼無法通過編譯的 */ public int i = 5; //值類型 public System.Exception ex = new Exception(); //引用類型 }
在 MyStruct結(jié)構(gòu)中,有2個(gè)字段,一個(gè)是值類型的i變量,一個(gè)是引用類型的ex變量。這種情況下,內(nèi)存中應(yīng)該是一個(gè)什么模樣呢?
首先,變量 i和ex作為MyStruct的成員,必然存放在MyStruct實(shí)例的內(nèi)部,而變量i作為值類型,其值就存放在自身;ex作為引用類型,變量內(nèi)只存放實(shí)例(對象)的引用,而實(shí)例(對象)則在堆上創(chuàng)建,因此就有如圖-5所示:
- 引用類型
且看下面的類型定義代碼:
public class MyClass { MyStruct ms = new MyStruct(); //上面所述的MyStruct結(jié)構(gòu) System.Random r = new Random(); //引用類型 }
在 MyClass中,有2個(gè)字段成員,一個(gè)是我們上面的所定義的MyStruct結(jié)構(gòu)值類型ms,另外一個(gè)是Random類類型r。
這里我們把情況再變得復(fù)雜一些了,因?yàn)?MyStruct內(nèi)部還有值類型和引用類型的字段,這時(shí)候內(nèi)存中是一幅什么景象呢?我們要記住,不管情況多么復(fù)雜,把握住值類型和引用類型的特點(diǎn),慢慢分析,總會(huì)得到正確的結(jié)果,正如圖-6所示:
作為引用類型的實(shí)例(對象),無論什么情況,都是在堆中的。而 MyStruct結(jié)構(gòu)作為MyClass的成員,它也在MyClass實(shí)例所占的堆內(nèi)存中,而且因?yàn)橹殿愋偷闹凳窃谧陨泶娣诺模跃褪菆D-6中看到的結(jié)果。整個(gè)圖-6,所有的值類型和引用類型的布局,都完全負(fù)責(zé)值類型和引用類型的特點(diǎn),沒有例外。
- 總結(jié)
以前在問起值類型和引用類型有什么區(qū)別的時(shí)候,經(jīng)常聽到同學(xué)說“值類型存放在棧上,引用類型存放在堆上”。其實(shí)這么說并不嚴(yán)謹(jǐn),因?yàn)楫?dāng)值類型作為引用類型的一個(gè)成員的時(shí)候,它的值是內(nèi)嵌在引用類型實(shí)例內(nèi)部在堆上存放的。我認(rèn)為,正確的說法應(yīng)該是:值類型變量的值存放在變量內(nèi)部,而引用類型變量的值存放在堆上,變量本身存放一個(gè)指向堆中的值的引用。同時(shí)我們也可以看到在 2個(gè)變量賦值的時(shí)候,值類型和引用類型的差別,值類型將自身的值復(fù)制給對方,之后,2方互不相干;引用類型把引用復(fù)制給對方,從而雙方都指向同一個(gè)堆中的實(shí)例,其中任何一方對實(shí)例做出修改,都會(huì)在另一方的操作中得到反映。最后我們通過復(fù)雜類型的內(nèi)部成員的內(nèi)存布局情況,進(jìn)一步了解了值類型和引用類型的內(nèi)存布局情況。
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
