c 中值型別和引用型別的基礎教程

2022-09-26 02:21:13 字數 3094 閱讀 3111

前言

值型別和引用型別,是c#比較基礎,也必須掌握的知識點,但是也不是那麼輕易就能掌握,今天跟著老胡一起來看看吧。 

典型型別

首先我們看看這兩種不同的型別有哪些比較典型的代表。 

典型值型別

int, long, float, double等原始型別中表示數字的型別都是值型別,表示時間的datatime也是值型別,除此之外我們還可以通過關鍵字struct自定義值型別。 

典型引用型別

原始型別中,array, list, dictionary, queue, stack和string都是引用型別,除此之外我們通過關鍵字class自定義引用型別。 

基類c#中所有的型別都最終繼承自object,這是沒有疑問的,但是這其中還有些微區別。 

值型別基類

對於值型別來說,除了最終繼承自object,還繼承自valuetype,繼承鏈如下

但是請不要誤解,這裡僅僅指的是值型別天然是valuetype,但是不代表值型別能夠這麼宣告

struct struct1 : valuetype

這樣是會引起編譯錯誤的,值型別不能繼承任何其他型別,值型別只能實現介面,不能繼承自其它型別。只有引用型別既可以實現介面也能繼承自其它型別。順便說一下,還有一點比較重要的是,valuetype重寫了object基類的equals方法和gethashcode方法,所以當使用equals比較兩個值型別的時候,系統會比較兩個值型別的各個屬性是否相等,再返回結果,這就是所謂的相等性。與此相對,引用型別在使用equals的時候,會在後台呼叫object.referenceequals,換言之,引用型別在比較相等性的時候會考慮同一性。 

引用型別基類

對於引用型別就沒有那麼麻煩,引用型別不會繼承自valuetype。引用型別可以繼承其他型別。 

在記憶體中的表現

我們都知道,c#將記憶體分為了兩部分,乙個是stack,另外乙個是managed heap。一般來說,用於函式呼叫進棧,函式返回出棧,用的是stack,而當創造乙個新的例項時,會根據建立的例項屬於值型別還是引用型別決定使用stack還是managed heap。 

值型別在記憶體中

當建立乙個值型別物件時,c#會在stack上面建立一塊空間,這塊空間就存放這個值型別物件。

int是乙個典型的值型別,如下語句

int age = 10;

會存在於記憶體中的stack上面。

如果把值型別的例項賦值給另外乙個值型別,那麼效果就是複製乙個新的值型別例項。

int myage = age;

引用型別在記憶體中

與值型別在記憶體中的表現不一樣,建立乙個引用型別的例項,不但會在stack上面新建乙個引用,還會在heap上面劃分出記憶體以容納該引用型別例項。使用者在使用的時候通過stack上面的變數間接引用該例項。

class author

public int age

}author author = new author();

注意看和程式設計客棧值型別在記憶體中的區別,引用型別通過stack上的變數訪問位於heap上面的例項。

在賦值的時候,拷貝的僅僅是stack上面的變數,新拷貝出來的物件和舊的物件指向的是同一塊記憶體。

author myauthor = author;

這個時候,author和myauthor指向同一塊記憶體,稱為同一性,通過呼叫

object.referenceequals(myauthor, author);

可以得到驗證。

但可能有細心的朋友會有疑問了,不是說int是值型別,值型別是存在於stack上面的嗎?為什麼在author類裡面,它會在heap裡面呢?贊乙個細心!值型別一般存在於stack上面,但如果某個值型別包含於引用型別,那麼它也會隨著那個引用型別存放在heap上面。 

當引數時的行為區別

c#中的引數傳遞預設都是傳值(by value),但是根據所傳遞物件是值型別還是引用型別,它們的行為還是有所區別,現在我們來看看。

值型別當引數

值型別當引數的時候,傳遞到函式內部的是乙份值型別的拷貝,所以在函式內部修改這個拷貝不會影響原物件。除非我們在傳遞引數的時候使用了ref或者out。

引用型別當引數

如果引數是引用型別,傳遞到函式內部的依然是乙份拷貝,但是這個拷貝是其在stack上面的變數的拷貝,就像上面的賦值那個例子。所以這個時候這份拷貝其實和原物件指向同一塊記憶體(指向同一性),修改這個物件可以反映到原物件上面。 

謹慎返回引用型別

程式設計是一項需要謹慎的工作,有時候我們經常會犯一些錯誤,而這些錯誤又是那麼的不明顯以至於不摔坑幾次,我們根本察覺不了,考慮下面乙個例子。

class people

public int age

private people _father = null;

public people father }

public people(people father)

public void showfather()

}class program

; people son = new people(father);

son.showfather();

console.readline();

} }看起來沒什麼問題,對吧?father沒有提供setter,似乎是安全的。但是我們試試下面的**。

static void main(string args)

; people son = new people(father);

var f = son.father;

f.name="changed";

son.showfather();

coifipubnsole.readline();

}看程式設計客棧,發現了什麼,外部改變了本來應該被封裝所保護的father屬性,封裝被破壞了!

稍微一想我們應該能明白這個道理,father屬性返回的拷貝的變數和原father變數指向同一塊例項。要想解決這個問題,我們要麼返回乙個值型別,要麼返回乙個全新的物件。修改father屬性如下:

public people father ; } }

再次測試,

這次封裝就沒問題了。 

總結今天也簡單的提到了比較時的同一性,和預防封裝被破壞所採用的返回乙個新的例項拷貝的策略(這個時候適合使用deepcopy),我們之後有機會再詳細聊。

C 中值型別和引用型別

概念 1.值型別 資料儲存在記憶體的堆疊中,從堆疊中可以快速地訪問這些資料,因此,值型別表示實際的資料。2.引用型別 表示指向儲存在記憶體堆中的資料的指標或引用 包括類 介面 陣列和字串 c 中定義的值型別包括原型別 sbyte byte short ushort int uint long ulo...

c 中值型別和引用型別

在c 中有兩種型別的資料,一種是值型別資料,一種是引用型別資料。在編碼的時候區分這兩種型別資料,可以避免一些細小的編碼錯誤。首先說說什麼型別是值型別,例如 int float bool之類的基礎型別,以及用struct定義的型別,如 datetime。除此外,如string,陣列,以及用class定...

C 中值型別和引用型別

本文將介紹c 型別系統中的值型別和引用型別,以及兩者之間的一些區別。同時,還會介紹一下裝箱和拆箱操作。首先,我們看看在c 中哪些型別是值型別,哪些型別是引用型別。值型別 結構型別 struct 列舉型別 enum 引用型別 變數的初始化中,都會有乙個預設值,在c 中,我們可以通過default關鍵字...