本文將介紹C#類型系統中的值類型和引用類型,以及兩者之間的一些區別。同時,還會介紹一下裝箱和拆箱操作。
首先,我們看看在C#中哪些類型是值類型,哪些類型是引用類型。
值類型:
引用類型:
變量的初始化中,都會有一個默認值,在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的進程運行空間。我們所要討論的棧和堆就存放在這個4GB的空間中。
在C#中,棧(Stack)是指調用棧(call 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#中的值類型和引用類型,以及棧和堆的基本概念。然後分析了值類型和引用類型在棧和堆中的存放。
同時,我們也了解到了: