標籤:
原文:C#中實值型別和參考型別
本文將介紹C#類型系統中的實值型別和參考型別,以及兩者之間的一些區別。同時,還會介紹一下裝箱和拆箱操作。
實值型別和參考型別
首先,我們看看在C#中哪些類型是實值型別,哪些類型是參考型別。
實值型別:
- 基礎資料類型(string類型除外):包括整型、浮點型、十進位型、布爾型。
- 整型(sbyte、byte、char、short、ushort、int、uint、long、ulong )
- 浮點型(float 和 double )
- 十進位型(decimal )
- 布爾型(bool )
- 結構類型(struct)
- 枚舉類型(enum)
參考型別:
- class、interface、delegate、object、string、Array
預設值
變數的初始化中,都會有一個預設值,在C#中,我們可以通過default關鍵字去查看某個類型的預設值。
通過default(int)可以看到,int的預設值是0,default(bool)顯示布爾類型的預設值是false。
對於所有的參考型別,預設值都會是null。
注意,這裡有個特殊的情況就是結構struct,如果對一個結構進行default操作,我們將得到每個結構成員的初始值狀態。也就是說,實值型別成員賦予實值型別的預設值,參考型別成員賦予參考型別的預設值。
簡單對比實值型別和參考型別
下面,我們通過一個簡單的例子看看。假設有一個Point類型,有x和y兩個座標成員。
同樣是下面一段代碼
Point p1 = new Point(5,9);Point p2 = p1;
如果Point類型是通過結構struct實現,那麼p2將會是p1的一個副本,也就是說任何一個的修改都不會影響另外一個;如果Point類型是通過類class實現,那麼p2和p1的引用值將會指向同一個對象。
為了進一步瞭解實值型別和參考型別,我們需要介紹一下棧和堆這兩個基本概念。
棧和堆
當我們在32為系統上運行一個程式的時候,這個程式就會有一個4GB的進程Runspace。我們所要討論的棧和堆就存放在這個4GB的空間中。
棧和堆的簡介
在C#中,棧(Stack)是指堆棧;堆(Heap)是指託管堆,由.NET垃圾收集器自動管理。
這裡就不對棧和堆進行詳細的分析了,只是舉一個簡單的例子來大致描述棧和堆的工作原理。
可以看到,局部變數在棧上的變化(入棧),當函數執行結束後,棧上的空間將會被清理;但是我們在堆上分配的空間始終從在,只能等待GC去幫我們清理不會被引用到的空間。
實值型別和參考型別的存放
介紹過棧和堆之後,下面我們看看實值型別和參考型別是怎麼存放的。
對於實值型別的變數,這個變數本身就代表這個實值型別的值;但是,對於參考型別的變數,這個參考型別的執行個體是在託管堆上分配的空間,而這個變數本身只是代表一個指向託管堆執行個體的引用(指標)。
所以這裡,我們可以對實值型別和參考型別變數的儲存有兩個概括:
- 參考型別永遠儲存在堆裡
- 實值型別和引用(指標)永遠儲存在它們聲明時所在的堆或棧裡
- 如果一個實值型別不是在方法中定義的,而是在一個參考型別裡,那麼此實值型別將會被放在這個參考型別裡並儲存在堆上
注意:根據上面第二點概括,可以得到"實值型別一定儲存在棧中"這個說法是錯誤的。例如,我們有一個Student類,在這個類中的Age屬性是一個實值型別,但是這個實值型別是儲存在Student類執行個體的空間中,也就是在堆上。
class Student{ public string Name { get; set; } public int Age{ get; set; }} 裝箱和拆箱
由於C#中所有的資料類型都是由基類System.Object繼承而來的,所以實值型別和參考型別的值可以通過顯式(或隱式)操作相互轉換。
這裡,可以將裝箱和拆箱描述為:
- 裝箱是將實值型別轉換為參考型別
- 拆箱是將參考型別轉換為實值型別
裝箱/拆箱的內部操作
其實,在裝箱和拆箱的過程中都對應一系列的轉換,這裡就通過表示了。
在實值型別進行裝箱時,產生的是全新的引用對象,這會有時間損耗,也就是造成效率降低。所以在C# 2.0中就引入了泛型來減少裝箱操作和拆箱操作消耗。
總結
本文介紹了C#中的實值型別和參考型別,以及棧和堆的基本概念。然後分析了實值型別和參考型別在棧和堆中的存放。
同時,我們也瞭解到了:
- 當使用參考型別時,我們是在和指向參考型別的引用(指標)打交道,而不是參考型別本身
- 當使用實值型別時,我們是在和實值型別本身打交道
C#中實值型別和參考型別