引言
山重水復疑無路,柳暗花明又一村,越探究越接近事物的本質。最近在學習原型模式(Prototype)時,發現原型模式本質就是對一個類原始數據的克隆,但在學習深拷貝和淺拷貝時又發現與值類型和引用類型有着千絲萬縷的聯系。回想好久都沒有溫習基礎,於是就整理了值類型和引用類型的隨筆,本文內容比較基礎,對於想繼續深入研究的同學可以查看IL更深入探究。
1.值類型(ValueType)
值類型包括:數值類型,結構體,bool型,用戶定義的結構體,枚舉,可空類型。
值類型的變量直接存儲數據,分配在托管棧中。變量會在創建它們的方法返回時自動釋放,例如在一個方法中聲明Char型的變量name=’C’,當實例化它的方法結束時,name變量在棧上占用的內存就會自動釋放
C#的所有值類型均隱式派生自System.ValueType。
結構體:struct(直接派生於System.ValueType)。
數值類型:整型,sbyte(System.SByte的別 名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char)。
浮點型:float(System.Single),double(System.Double)。
財務計算的高精度decimal型:decimal(System.Decimal)。
bool型:bool(System.Boolean的別名)。
用戶定義的結構體(派生於System.ValueType)。
枚舉:enum(派生於System.Enum)。
可空類型(派生於System.Nullable<T>泛型結構體,T?實際上是System.Nullable<T>的別名
2.引用類型(ReferenceType)
引用類型包括:數組,用戶定義的類、接口、委托,object,字符串,null類型,類。
引用類型的變量持有的是數據的引用,數據存儲在數據堆,分配在托管堆中,變量並不會在創建它們的方法結束時釋放內存,它們所占用的內存會被CLR中的垃圾回收機制釋放。
數組(派生於System.Array)
用戶需定義以下類型:
類:class(派生於System.Object);
接口:interface(接口不是一個“東西”,所以不存在派生於何處的問題。接口只是表示一種contract約定[contract])。
委托:delegate(派生於System.Delegate)。
object(System.Object的別名);
字符串:string(System.String的別名)。
3.值類型與引用類型區別:
|
值類型 |
引用類型 |
存儲方式 |
直接存儲數據本身 |
存儲的是數據的引用,數據存儲在數據堆中 |
內存分配 |
分配在棧中的 |
分配在堆中 |
效率 |
效率高,不需要地址轉換 |
效率較低,需要進行地址轉換 |
內存回收 |
使用完后立即回收 |
使用完后不立即回收,而是交給GC處理回收 |
賦值操作 |
創建一個新對象 |
創建一個引用 |
類型擴展 |
不易擴展,所有值類型都是密封(seal)的,所以無法派生出新的值類型 |
具有多態的特性方便擴展 |
實例分配 |
通常是在線程棧上分配的(靜態分配),但是在某些情形下可以存儲在堆中 |
總是在進程堆中分配(動態分配) |
值類型和引用類型樹形結構:
注:給參數加了fef(out)后,參數是引用傳遞,這時候傳遞的是棧地址(指針,引用),否則就是正常的值傳遞---棧原始數據的拷貝。
4.內存分配
值類型的實例經常會存儲在棧上的。但是也有特殊情況。如果某個類的實例有個值類型的字段,那么實際上該字段會和類實例保存在同一個地方,即堆中。不過引用類型的對象總是存儲在堆中。如果一個結構的字段是引用類型,那么只有引用本身是和結構實例存儲在一起的(在棧或堆上,視情況而定)。
引用類型在棧中存儲一個引用,其實際的存儲位置位於托管堆。簡稱引用類型部署在托管推上。值類型總是分配在它聲明的地方:作為字段時,跟隨其所屬的變量(實例)存儲;作為局部變量時,存儲在棧上。值類型在內存管理方面具有更好的效率,並且不支持多態,適合用做存儲數據的載體;引用類型支持多態,適合用於定義 應用程序的行為。
注:堆棧(stack)是一種后進先出的數據結構。在內存中,變量會被分配在堆棧上來進行操作。堆(heap)是用於為類型實例(對象)分配空間的內存區域,在堆上創建一個對象,會將對象的地址傳給堆棧上的變量(反過來叫變量指向此對象,或者變量引用此對象)。
5.裝箱和拆箱
1)裝箱就是將一個值類型轉換成等值的引用類型
在堆上為新生成的對象(該對象包含數據,對象本身沒有名稱)分配內存。
將堆棧上值類型變量的值拷貝到堆上的對象中。
將堆上創建的對象的地址返回給引用類型變量(從程序員角度看,這個變量的名稱就好像堆上對象的名稱一樣)。
2)拆箱就是將一個引用類型轉換成等值的值類型
將引用類型變量堆上的值拷貝到棧上面。
總結
值類型和引用類型理解透徹后,我們知道C#里面是值傳遞,但是有些變量是引用類型的,在傳遞和拷貝時需要特別注意。方法傳遞參數時加上ref(out),為引用傳遞參數。
值傳遞僅僅傳遞的是值,不影響原始值。
引用傳遞,傳遞的是內存地址,修改后會改變內存地址對應儲存的值。
備注:
作者:Shengming Zeng
博客:http://www.cnblogs.com/zengming/
本文是原創,歡迎大家轉載;但轉載時必須注明文章來源,且在文章開頭明顯處給明鏈接。
<歡迎有不同想法或見解的同學一起探討,共同進步>