1. 程式人生 > 其它 >值型別與引用型別

值型別與引用型別

技術標籤:C#

值型別與引用型別

引用型別:class,陣列(即使元素型別是值型別),委託,介面

值型別:struct,列舉

差異點:

1.儲存位置不同:見誤區第二點

2.值型別不可以派生出其他型別:

對於引用型別來說,每個物件的開頭都包含一個數據塊,它標識了物件的實際型別,同時還提供了其他的一些資訊.

永遠不能改變物件的型別,當執行簡單的強制型別轉換時,執行時會獲取一個引用,檢查它引用的物件是不是目標型別的一個有效物件.如果有效就返回原始引用,否則就丟擲異常.

引用本身並不知道物件的型別,所有同一個引用"值"可用於(引用)不同型別的多個變數.

Stream stream=new MemoryStream();
MemoryStream memorystream=(MemoryStream)stream;

第二行檢查stream的值,引用的是不是一個MemoryStream(或子類)物件,並將memorystream的值設為相同的值.

關於值型別和引用型別的誤區

1.結構是輕量級的類

結構和類都可以擁有函式

從使用角度上看:具體使用結構還是類,要根據設計時所需要的語義,值型別或者是引用型別,而不是根據這個型別簡單與否.(eg.DateTime被作為結構但也提供了相應的操作函式).

從效能角度上看:結構(值型別),它不需要垃圾回收(除非被裝箱).類(引用型別),它在作為函式引數等操作時,只需要複製較少的位元組而結構需要整個被複制.兩者都有不同的適用情況,要從具體的上下文來決定.不能簡單的說結構比類更加輕量(高效).

不管是類還是結構,擁有多少函式並不重要,每個關於結構或者類的例項所佔用的記憶體不會受到影響.(程式碼本身會消耗記憶體,但這隻會發生一次,而不是每個例項都發生). 程式碼在儲存時消耗一次記憶體,在呼叫物件的函式的時候,實際上是通過定址到函式的地址進行呼叫,可以理解為同一型別的物件共享同一片程式碼

2.引用型別儲存在堆上,值型別儲存在棧上

第一部分是正確的,引用型別儲存在堆上.靜態變數也儲存在堆上

但第二部分有問題,變數的值是在它宣告的位置儲存的.所以假定一個類中有一個int型別的成員變數,那麼在這個類的任何例項中,該變數的值總是和該例項的其他資料在一起,也就是在堆上.

只有區域性變數(函式內部宣告的變數)和函式引數在棧上

.(有例外,如匿名函式)也就是說引用型別的函式引數(注意這是個引用不是實際的物件)的也是儲存在棧上的

3.物件在C#中預設是通過引用傳遞

先理解傳遞分為值傳遞和引用傳遞,無論是引用傳遞還是值傳遞,永遠不會傳遞物件本身.

  • 值傳遞:就是直接傳遞,這樣值型別是一個副本,引用型別是一個引用

  • 引用傳遞:就是在傳入的引數之前新增ref和out關鍵字,這樣值型別和引用型別都會改變原始的變數

引用型別作為函式引數使用時,引數預設是以值傳遞方法來傳遞的,但值(括號內的形參)本身是一個引用.

裝箱和拆箱

裝箱:它允許根據值型別建立一個物件,然後使用對這個新物件的一個引用.

拆箱:裝箱的逆過程,複製箱內的值,在賦值後,和引用型別不再有關係,如下程式碼塊中的o和j.

int i=5;
object 0=i;//裝箱,o是一個新物件的一個引用,用當前i的值建立的
int j=(int)o//拆箱,對j的改變不會改變i,j是值型別
//到這,三者之間再也沒有任何關係,2個值型別,一個引用型別

隱式發生的裝箱:

1.為一個型別的值呼叫ToString,Equals,GetHashCode方法時,如果該型別沒有覆蓋這些方法,也會發生裝箱.

2.將值賦給一個介面型別的變數,或者把它作為介面型別的引數來傳遞,也會發生裝箱.(eg.IComparable x=5,會對數字5進行裝箱).

裝箱和拆箱會加劇程式本身的操作開銷,還會建立數量龐大的物件,而這些物件會加重垃圾回收器的負擔.