直白點兒說:值型別就是現金,要用直接用;引用型別是存摺,要用還得先去銀行取現。
宣告乙個值型別變數,編譯器會在棧上分配乙個空間,這個空間對應著該值型別變數,空間裡儲存的就是該變數的值。引用型別的例項分配在堆上,新建乙個引用型別例項,得到的變數值對應的是該例項的記憶體分配位址,這就像您的銀行賬號一樣。具體哪些型別是值型別哪些是引用型別,大家翻翻書,背一背就好了,不過我想,做過一段時間的開發,即使您背不了書上教條的定義,也不會把值型別和引用型別搞混的。接下來,還是老規矩,咱看碼說話吧。
1:public
class person
2:
4:public
int age
5: }
6:
7:public
static
class referenceandvalue
8: ;
12: person anders = new person ;
13:
14:int age = zerocool.age;
15: zerocool.age = 22;
16:
17: person guru = anders;
18: anders.name = "anders hejlsberg";
19:
20: console.writeline("zerocool's age:\t", zerocool.age);
21: console.writeline("age's value:\t", age);
22: console.writeline("anders' name:\t", anders.name);
23: console.writeline("guru' name:\t", guru.name);
24: }
25: }上面這段**,我們首先建立了乙個person類,包含了name和age兩個屬性,毋庸置疑,person類是引用型別,name也是,因為它是string型別的(但string是很特殊的引用型別,後面將專門有一篇文章來討論),但age則是值型別。接下來我們來看看demonstration方法,其中演示的就是值型別跟引用型別的區別。
首先,我們宣告了兩個person類的例項物件,zerocool和anders,前面提到過,這兩個物件都被分配在堆上,而zerocool和anders本身其實只是物件所在記憶體區域的起始位址引用,換句話說就是指向這裡的指標。我們宣告物件例項時也順便分別進行了初始化,首先我們看,zerocool物件的值型別成員,我們賦值為25(對,我今年25歲),anders(待會兒你們就知道是誰了)的name屬性,我們賦值為「anders」。齊活兒,接下來看我們怎麼幹吧。
我們宣告乙個值型別變數age,直接在初始化時把zerocool的age值賦給它,顯然,age的值就是25了。但這個時候zerocool不高興了,他想裝嫩,私自把自己的年齡改成22歲,剛夠法定結婚年齡。然後我們又宣告了乙個引用型別的guy物件,初始化時就把anders賦給它,然後anders露出廬山真面目了,他的名字叫「anders hejlsberg」(在此向c#之父致敬)。接下來我們來分別答應出這幾個變數的值,看看有什麼差別。
你可能要覺得奇怪(你要不覺得奇怪,也就不用再接著往下看了),為什麼我們改了zerocool.age的值,age沒跟著變,改了anders.name的值,guru.name卻跟著變了呢?這就是值型別和引用型別的區別。我們宣告age值型別變數,並將zerocool.age賦給它,編譯器在棧上分配了一塊空間,然後把zerocool.age的值填進去,僅此而已,二者並無任何牽連,就像影印機一樣,只是把zerocool.age的值拷貝給age了。而引用型別不一樣,我們在宣告guy的時候把anders賦給它,前面說過,引用型別包含的是只想堆上資料區域位址的引用,其實就是把anders的引用也賦給guy了,因此這二者從此指向了同一塊記憶體區域,既然是指向同一塊區域,那麼甭管誰動了裡面的「乳酪」,另乙個變現出來的結果也會跟著變,就像信用卡跟親情卡一樣,用親情卡取了錢,與之關聯的信用卡賬上也會跟著發生變化。一提到錢,估計大家夥兒印象就深了些吧,呵呵!
另外,效能上也會有區別的。既然乙個是直接操作記憶體,另乙個則多一步先解析引用位址,那麼顯然很多時候值型別會減小系統效能開銷。但「很多時候」不代表「所有時候」,有些時候還得量力而為,例如需要大量進行函式引數傳遞或返回的時候,老是這樣進行字段拷貝,其實反而會降低應用程式效能。另外,如果例項會被頻繁地用於hashtable或者arraylist之類的集合中,這些類會對其中的值型別變數進行裝箱操作,這也會導致額外的記憶體分配和記憶體拷貝操作,從應用程式效能方面來看,其實也不划算。
哦對了,上面提到了乙個概念,裝箱。那麼什麼是裝箱呢?其實裝箱就是值型別到引用型別的轉化過程。將乙個值型別變數裝箱成乙個引用型別變數,首先會在託管堆上為新的引用型別變數分配記憶體空間,然後將值型別變數拷貝到託管堆上新分配的物件記憶體中,最後返回新分配的物件記憶體位址。裝箱操作是可逆的,所以還有拆箱操作。拆箱操作獲取指向物件中包含值型別部分的指標,然後由程式設計師手動將其對應的值拷貝給值型別變數。接下來我們來看看典型的裝箱和拆箱操作。
1:public
static
class boxingandunboxing
2:
17: }在該方法中,我們首先宣告了乙個值型別變數ageint,但並未給它賦值,接著宣告了乙個典型的引用型別變數ageobject,並把ageint賦給它,這裡就進行了一次裝箱操作。編譯器現在託管堆上分配一塊記憶體空間(空間大小為物件中包含的值型別變數所佔空間總和外加乙個方法表指標和乙個syncblockindex),然後把ageint拷貝到這個空間中,再返回該空間的引用位址。接下來第13行則是拆箱操作,編譯器獲取到ageobject物件中值型別變數的指標,然後將其值拷貝給值型別變數。如果你把第10行注釋掉的**開啟(這是通俗說法,其實就是取消注釋),那麼第13行就會丟擲
system.nullreferenceexception
異常,要說問什麼,這又會牽扯出值型別跟引用型別另乙個大的不同。看見了吧,宣告ageint時並沒有賦值,如果關掉第10行**,程式不會報錯,最後列印出個0,這說明在宣告值型別變數時,如果沒有初始化賦值,編譯器會自動將其賦值為0,既然值型別沒有引用,那麼它就不可能為空。引用型別不一樣,它可以為空引用,一張過期作廢的銀行卡是可以存在。而如果將乙個空的物件拆箱,編譯器上哪兒去找它裡面的值型別變數的指標呢?所以這也是拆箱操作需要注意的地方。
最後,我們在把值型別和引用型別之間其它一些明顯區別大致羅列如下,以便大家能順利通過面試第一問。
c 引用型別和值型別區別
解析 clr支援兩種型別 值型別和引用型別。用jeffrey richter clr via c 作者 的話來說,不理解引用型別和值型別區別的程式設計師將會把 引入詭異的陷阱和諸多效能問題 這就要求我們正確理解和使用值型別和引用型別。值型別包括c 的基本型別 用關鍵字int char float等來...
C 值型別和引用型別的區別
c 中 變數型別分兩種 值型別和引用型別 由此引發兩種形式的記憶體空間 堆疊和託管堆 堆疊 又稱棧 存放所有值型別的資料 託管堆 存放所有引用型別的資料 值型別的變數本身就存放資料 而引用型別則儲存實際資料的引用 值型別有個特點,那就是大小都固定,比如乙個位元組的byte 4個位元組的int。即使資...
C 引用型別和值型別的區別
我們都知道,c 的兩大資料型別分別為值型別和引用型別。很多人或許閉著眼睛都能說出值型別包括簡單型別 結構體型別和列舉型別,引用型別包括自定義類 陣列 介面 委託等,但是當被問及到二者之間的聯絡和區別,什麼時候用struct什麼時候用class時,就常常混淆不清了。為此,了解值型別和引用型別的本質差異...