1. 程式人生 > >【C#】之 值型別、引用型別及其傳參

【C#】之 值型別、引用型別及其傳參

前言

值型別和引用型別都是相對於變數來說的,是變數儲存資料的一種形式。

值型別變數直接儲存資料將資料儲存在棧中,而引用型別的變數儲存的是資料的引用,其真正的資料儲存在資料堆中。

棧與堆:
是在編譯期間就分配好的記憶體空間,因此你的程式碼中必須就棧的大小有明確的定義。
是程式執行期間動態分配的記憶體空間,可以根據程式的執行情況確定要分配的堆記憶體的大小。

一、值型別(ValueType)

1、範圍

數值型別:整型、int、long、byte、char、double
結構體:struct、使用者定義的結構體
bool型
列舉:enum

2、說明

值型別的變數聲明後,無論是否已經賦值,編譯器都會為其分配記憶體空間。
每個變數的記憶體空間大小可以不同,用來直接儲存資料本身(存放到棧上)。
當資料被釋放時,期記憶體空間將會被回收。
每進行一次賦值操作,都會建立一個新物件,為其分配記憶體儲存資料。

例如
宣告一個變數a,為其賦值為20,這時在棧上就會分配到一個記憶體空間,來儲存a的值20 。再宣告一個變數b,讓b=a,這時棧上會在分配出一個記憶體空間,用來儲存b的值。當a的值改變是,不會影響b的值改變。當b的值改變時也不會影響到a的值改變。

二、引用型別(ReferenceType)

1、範圍

包括陣列、使用者定義的類、介面interface、委託delegate、object、字串string、null型別、類class

2、說明

引用型別的變數持有的是對資料的引用,即申請引用型變數後,記憶體會為其分配一個很小的空間(空間大小一般相等)用來儲存地址(在棧中),而這個地址對應的就是資料在堆中的位置。引用型變數持有的就是這個資料的地址。當新的變數的值在堆中存在時,堆中並不會重新增加一個相同的值,而是在棧中會有一個新的記憶體來存放這個新變數所持有的引用(即原來資料在堆中的地址)。

變數並不會在建立他們的方法結束時釋放記憶體空間,他們所佔用的記憶體會被CLR中的垃圾回收機制釋放。
每進行一個賦值操作都會建立一個新的引用。


申請一個變數a,為其賦值為20 ,在堆中會分配出記憶體來儲存20這個資料,20在堆中的地址會儲存在棧中,a持有這個地址。再申請一個變數b,使b=a,棧中會將a持有的這個地址同時賦值給b,a和b同時擁有堆中20這個資料的地址。當改變b=10,堆中的這個20的資料會變為10,而此時a與b用的是同一個地址,所以此時a的值也會變為10.

三、值型別與引用型別的區別

角度 值型別 引用型別
儲存方式 直接儲存資料本身 儲存的是資料的引用,資料儲存在堆中
記憶體分配 分配在棧中 分配在堆中
效率 高,不需要地址轉換 低,需要地址轉換
記憶體回收 使用完後立即回收 使用完後不立即回收,而是給GC處理
賦值操作 建立一個新物件 建立一個引用
型別擴充套件 不易擴充套件,所有型別都是封閉的,無法派生出新的型別 具有多型性,方便擴充套件
例項分配 線上程棧上分配,靜態分配(有時可以動態) 在程序堆中分配,動態分配

總結:
值傳遞僅僅傳遞的是值,不影響原始值。
引用傳遞,傳遞的是記憶體地址,修改後會改變記憶體地址對應儲存的值。

四、裝箱和拆箱

1、裝箱

將值型別轉變成引用型別
在堆上為新生成的物件(該物件包含資料,物件本身沒有名稱)分配記憶體。

將堆疊上值型別變數的值拷貝到堆上的物件中。

將堆上建立的物件的地址返回給引用型別變數(從程式設計師角度看,這個變數的名稱就好像堆上物件的名稱一樣)。

2、拆箱

將引用型變數變成值型別
將引用型別變數堆上的值拷貝到棧上面。

3、比較

名稱 | 值型別 | 引用型別
表示型別 | 基本型別 | 類,陣列,介面 ,C#特有的委託.
儲存內容 | 值 | 值的引用
儲存位置 | 堆疊 | 託管堆

五、傳參

在C#中很多變數都是按值傳遞的,但是也有個別的變數是引用型別的,方法傳遞引數時加上ref(out),為引用傳遞引數,使用時都會改變原來引數的數值。

ref可以把引數的數值傳遞進函式,需要在外面進行初始化,但是out是要把引數清空,就是說無法把一個數值從out傳遞進去的,out進去後,引數的數值為空,所以必須在函式內部初始化一次。

說明:
1、使用ref型引數時,傳入的引數必須先被初始化。對out而言,必須在方法中對其完成初始化。

2、使用ref和out時,在方法的引數和執行方法時,都要加Ref或Out關鍵字。以滿足匹配。

3、out適合用在需要retrun多個返回值的地方,而ref則用在需要被呼叫的方法修改呼叫者的引用的時候。

好多部落格上說ref是有進有出,out是隻進不出,想一想好像真的是這樣。