1. 程式人生 > >值型別和引用型別的區別 .

值型別和引用型別的區別 .

在開始探討.NET Framework的底層型別系統時,常常會聽到一些相互矛盾的說法。一方面,“所有的型別都繼承於Object類”,另一方面“在值型別和引用型別之間轉換時要特別小心”。搞清楚這些說法的關鍵在於,要記住——每種型別,無論是內建的結構,如整型或字串型,還是定製類,如MyEmployee,都繼承於Object類。值型別和引用型別的主要區別在於底層實現方式的不同。

值型別和引用型別的區別是一個很好的學習起點,因為這是一個相對簡單的區別。更重要的是,作為.NET開發人員,如果編寫的不是效能要求特別高的程式碼,一般不需要注意這個區別。

在把資料賦給值型別和引用型別的時候,兩者表現完全不同:

●       在把資料賦給一個值型別時,資料儲存在堆疊上的變數中。

●       在把資料賦給一個引用型別時,變數中只儲存一個引用,資料則儲存在託管堆上。

理解堆疊和堆之間的區別是非常重要的。堆疊是一個比較小的記憶體區域,程序和執行緒在其中儲存大小固定的資料塊。例如,無論資料的實際值是多少,儲存整數和小數型別所需要的位元組數都不會變化。因此這種變數在堆疊中的位置可以高效地確定(當一個過程需要提取某個變數時,就必須搜尋堆疊。如果堆疊包含的變數具有動態的記憶體大小,這種搜尋就需要較長的時間)。

引用型別沒有固定的大小。例如,字串的大小可以在2位元組到接近系統中所有的可用記憶體之間變化。引用型別大小的不確定性意味著,它們包含的資料更適合儲存在堆上,而不是堆疊上,但是,引用型別的地址(即資料在堆上的位置)有固定的大小,所以可以儲存在堆疊上。把引用儲存在堆疊上,整個程式的執行速度會快得多,因為程序可以快速定位與變數中的資料。

固定大小的變數和大小動態變化的變數分別儲存在堆疊和堆上,會使對這兩種變數的操作方式產生差異。下面通過比較System.Drawing.Point結構(一種值型別)和System.Text. StringBuilder類(一種引用型別)來說明這一點。

Point結構是.NET圖形庫的一部分,而該圖形庫是System.Drawing名稱空間的一部分。StringBuilder類是System.Text名稱空間的一部分,用於高效地編輯字串。名稱空間將在第8章論述。

下面先看看如何使用System.Drawing.Point結構:

Dim ptX As New System.Drawing.Point(10, 20)

Dim ptY As New System.Drawing.Point

 ptY = ptX

ptX.X = 200

Console.WriteLine(ptY.ToString())

這個運算的輸出是{X=10, Y=20},這看起來是符合邏輯的。程式碼在將ptX複製到ptY後,包含在ptX中的資料就複製到堆疊上與ptY相關的位置上。當改變ptX的值時,只有堆疊上與ptX相關的記憶體被更新,改變ptX的值不會影響ptY。但引用型別不是這樣。考慮下面的程式碼,它使用了System.Text.StringBuilder類:

Dim objX As New System.Text.StringBuilder("Hello World")

Dim objY As System.Text.StringBuilder

objY = objX

objX.Replace("World", "Test")

Console.WriteLine(objY.ToString())

這段程式碼的執行結果是Hello Test,而不是Hello World。從上面使用Point示例可以看出,在把一個值型別賦給另一個值型別時,會複製儲存在堆疊上的資料。因此,在前一個例子中,將objY賦給objX時,堆疊上與objX相關的資料會複製到堆疊上與objY相關的資料上。但是,在本例中,複製的不是實際的資料,而是儲存在託管堆上的資料的地址,即objX和objY現在引用的是相同的資料。當堆上的資料發生變化時,如果某個變數儲存了對該記憶體的引用,則與該變數相關的資料就會發生變化。這就是引用型別的預設操作方式,稱為淺度複製(shallow copy)。本章的後面將論述字串上的這個操作方式是如何被重寫來執行深度複製的。

值型別和引用型別之間的差異不僅僅是在複製時它們的表現不同。本章的後面將討論物件所提供的其他功能。下面先看看最常用的值型別,理解.NET是如何使用它們的。

區別:
1、值型別通常被分配在棧上,它的變數直接包含變數的例項,使用效率比較高。

2、引用型別分配在託管堆上,引用型別的變數通常包含一個指向例項的指標,變數通過該指標來引用例項。

3、值型別繼承自ValueType(注意:而System.ValueType又繼承自System.Object);而引用型別繼承自System.Object。 

4、值型別變數包含其例項資料,每個變數儲存了其本身的資料拷貝(副本),因此在預設情況下,值型別的引數傳遞不會影響引數本身;而引用型別變數儲存了其資料的引用地址,因此以引用方式進行引數傳遞時會影響到引數本身,因為兩個變數會引用了記憶體中的同一塊地址。 

5、值型別有兩種表示:裝箱與拆箱;引用型別只有裝箱一種形式。我會在下節以專門的篇幅來深入討論這個話題。 

6、典型的值型別為:struct,enum以及大量的內建值型別;而能稱為類的都可以說是引用型別。 

7、值型別的記憶體不由GC(垃圾回收,Gabage Collection)控制,作用域結束時,值型別會自行釋放,減少了託管堆的壓力,因此具有效能上的優勢。例如,通常struct比class更高效;而引用型別的記憶體回收,由GC來完成,微軟甚至建議使用者最好不要自行釋放記憶體。 

8、值型別是密封的(sealed),因此值型別不能作為其他任何型別的基類,但是可以單繼承或者多繼承介面;而引用型別一般都有繼承性。 

9、值型別不具有多型性;而引用型別有多型性。 

10、值型別變數不可為null值,值型別都會自行初始化為0值;而引用型別變數預設情況下,建立為null值,表示沒有指向任何託管堆的引用地址。對值為null的引用型別的任何操作,都會丟擲NullReferenceException異常。 

11、值型別有兩種狀態:裝箱和未裝箱,執行庫提供了所有值型別的已裝箱形式;而引用型別通常只有一種形式:裝箱。