C# 值類型和引用類型

《CLR via C#》作者Jeffrey Richter的話來說,“不理解【引用類型】和【值類型】區(qū)別的程序員將會給代碼引入詭異的bug和性能問題(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance issues into their code.)”。這就要求我們正確理解和使用值類型和引用類型。

C#中數(shù)據(jù)類型為CTS(Common Type System),包括值類型和引用類型;值類型包含【布爾類型】、【字符型】和【枚舉類型】。

枚舉類型(enum)是由一組特定常量構(gòu)成的一組數(shù)據(jù)結(jié)構(gòu),是值類型的一種特殊形式,它從System.Enum繼承而來,并為基礎(chǔ)類型的值提供替代名詞。枚舉類型有名稱、基礎(chǔ)類型和一組字段。基礎(chǔ)類型必須是內(nèi)置的整數(shù)類型,字段是靜態(tài)文本字段,其中的每一個字段都表示常數(shù),同一個值可以分配給多個字段,出現(xiàn)這種情況時,必須將其中某個值標(biāo)記為主要枚舉值,以便進行反射和字符串轉(zhuǎn)換。枚舉聲明可以顯式地聲明 byte、sbyte、short、ushort、int、uint、long 或 ulong 類型作為對應(yīng)的基礎(chǔ)類型,沒有顯式地聲明基礎(chǔ)類型的枚舉聲明意味著所對應(yīng)的基礎(chǔ)類型是int。

用戶可以將基礎(chǔ)類型的值分配給枚舉,反之亦然(運行庫不要求強制轉(zhuǎn)換);也可創(chuàng)建枚舉的實例,并調(diào)用System.Enum的方法以及對枚舉基礎(chǔ)類型定義的任何方法。對于枚舉還有以下附加限制:

1.枚舉不能定義自己的方法

2.枚舉不能實現(xiàn)接口

3.枚舉不能定義屬性或事件

注意點:

1.對于沒有賦值的枚舉類型,聲明的第一個枚舉成員它的默值為0,以后的枚舉成員值是將前一個枚舉成員(按照文本順序)的值加1得到的。

2.允許多個枚舉成員有相同的值。沒有顯示賦值的枚舉成員的值,總是前一個枚舉成員的值加1。

3.使用時注意類型轉(zhuǎn)換。

char代表無符號的16位整數(shù),數(shù)值范圍從0~65535。 char類型的可能值對應(yīng)于統(tǒng)一字符編碼標(biāo)準(Unicode)的字符集。char類型與其他整數(shù)類型相比有以下兩點不同之處:

1.沒有其他類型到char類型的隱式轉(zhuǎn)換。即使是對于sbyte,byte和ushort這樣能完全使用char類型代表其值的類型, sbyte,byte和ushort到char的隱式轉(zhuǎn)換也不存在。

2.char類型的常量必須被寫為字符形式,如果用整數(shù)形式,則必須帶有類型轉(zhuǎn)換前綴。

比如(char)10賦值形式有三種: char chsomechar="A"; char chsomechar="\x0065"; 十六進制 char chsomechar="\u0065 ;

unicode表示法字符型中有下列轉(zhuǎn)義符:

\'用來表示單引號

\"用來表示雙引號

\\ 用來表示反斜杠

\0 表示空字符

\a 用來表示感嘆號

\b 用來表示退格

\f 用來表示換頁

\n 用來表示換行

\r 用來表示回車

\t 用來表示水平tab

\v 用來表示垂直tab

類型說明及范圍

float類型精確到小數(shù)點后面7位。double類型精確到小數(shù)點后面15位或16位。decimal關(guān)鍵字表示128位數(shù)據(jù)類型。同浮點型相比,decimal類型具有更高的精度和更小的范圍,這使它適合于財務(wù)和貨幣計算,精確到小數(shù)點后面28到29位。


1. 通用類型系統(tǒng)

C#中,變量是值還是引用僅取決于其數(shù)據(jù)類型。C#的基本數(shù)據(jù)類型都以平臺無關(guān)的方式來定義。C#的預(yù)定義類型并沒有內(nèi)置于語言中,而是內(nèi)置于.NET Framework中。.NET使用通用類型系統(tǒng)(CTS)定義了可以在中間語言(IL)中使用的預(yù)定義數(shù)據(jù)類型,所有面向.NET的語言都最終被編譯為 IL,即編譯為基于CTS類型的代碼。

例如,在C#中聲明一個int變量時,聲明的實際上是CTS中System.Int32的一個實例。這具有重要的意義:

確保IL上的強制類型安全;

實現(xiàn)了不同.NET語言的互操作性;

所有的數(shù)據(jù)類型都是對象。它們可以有方法,屬性,等。例如:

int i;

i = 1;

string s;

s = i.ToString();

MSDN的這張圖說明了CTS中各個類型是如何相關(guān)的。注意,類型的實例可以只是值類型或自描述類型,即使這些類型有子類別也是如此。

2. 值類型

C#的所有值類型均隱式派生自System.ValueType: 結(jié)構(gòu)體:struct(直接派生于System.ValueType);

數(shù)值類型:

整 型: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);

用于財務(wù)計算的高精度decimal型:decimal(System.Decimal)。

bool型:bool(System.Boolean的別名);

用戶定義的結(jié)構(gòu)體(派生于System.ValueType)。

枚舉:enum(派生于System.Enum);

可空類型(派生于System.Nullable泛型結(jié)構(gòu)體,T?實際上是System.Nullable的別名)。

每種值類型均有一個隱式的默認構(gòu)造函數(shù)來初始化該類型的默認值。例如:

int i = new int();

等價于:

Int32 i = new Int32();

等價于:

int i = 0;

等價于:

Int32 i = 0;

使用new運算符時,將調(diào)用特定類型的默認構(gòu)造函數(shù)并對變量賦以默認值。在上例中,默認構(gòu)造函數(shù)將值0賦給了i。MSDN上有完整的默認值表。

所有的值類型都是密封(seal)的,所以無法派生出新的值類型。

值得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一個類類型,而不是值類型。其關(guān)鍵在于ValueType重寫了Equals()方法,從而對值類型按照實例的值來比較,而不是引用地址來比較。

可以用Type.IsValueType屬性來判斷一個類型是否為值類型:

復(fù)制代碼 代碼如下:

TestType testType = new TestType ();

if (testType.GetType().IsValueType)

{

Console.WriteLine("{0} is value type.", testType.ToString());

}

3. 引用類型

C#有以下一些引用類型:

數(shù)組(派生于System.Array)

用戶定義的以下類型:

類:class(派生于System.Object);

接口:interface(接口不是一個“東西”,所以不存在派生于何處的問題。Anders在《C# Programming Language》中說,接口只是表示一種約定[contract]);

委托:delegate(派生于System.Delegate);

object:(System.Object的別名);

字符串:string(System.String的別名)。

可以看出:

引用類型與值類型相同的是,結(jié)構(gòu)體也可以實現(xiàn)接口;

引用類型可以派生出新的類型,而值類型不能;

引用類型可以包含null值,值類型不能(可空類型功能允許將 null 賦給值類型);

引用類型變量的賦值只復(fù)制對對象的引用,而不復(fù)制對象本身。而將一個值類型變量賦給另一個值類型變量時,將復(fù)制包含的值。

對于最后一條,經(jīng)常混淆的是string。我曾經(jīng)在一本書的一個早期版本上看到String變量比string變量效率高;我還經(jīng)常聽說String是引用類型,string是值類型,等等。例如:

string s1 = "Hello, ";

string s2 = "world!";

string s3 = s1 + s2; ? //s3 is "Hello, world!"

這確實看起來像一個值類型的賦值。再如:

string s1 = "a";

string s2 = s1;

s1 = "b"; ? //s2 is still "a"

改變s1的值對s2沒有影響。這更使string看起來像值類型。實際上,這是運算符重載的結(jié)果,當(dāng)s1被改變時,.NET在托管堆上為s1重新分配了內(nèi)存。這樣的目的,是為了將做為引用類型的string實現(xiàn)為通常語義下的字符串。

4. 值類型和引用類型在內(nèi)存中的部署

經(jīng)常聽說,并且經(jīng)常在書上看到:值類型部署在棧上,引用類型部署在托管堆上。實際上并沒有這么簡單。

MSDN上說:托管堆上部署了所有引用類型。這很容易理解。當(dāng)創(chuàng)建一個應(yīng)用類型變量時:

object reference = new object();

關(guān)鍵字new將在托管堆上分配內(nèi)存空間,并返回一個該內(nèi)存空間的地址。左邊的reference位于棧上,是一個引用,存儲著一個內(nèi)存地址;而這個地址指向的內(nèi)存(位于托管堆)里存儲著其內(nèi)容(一個System.Object的實例)。下面為了方便,簡稱引用類型部署在托管推上。

再來看值類型。《C#語言規(guī)范》 上的措辭是“結(jié)構(gòu)體不要求在堆上分配內(nèi)存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“結(jié)構(gòu)體在棧上分配內(nèi)存”。這不免容易讓人感到困惑:值類型究竟部署在什么地方?

5. 正確使用值類型和引用類型

這一部分主要參考《Effective C#》,希望能讓你加深對值類型和引用類型的理解。

C#中,變量是值還是引用僅取決于其數(shù)據(jù)類型。

C#的值類型包括:結(jié)構(gòu)體(數(shù)值類型,bool型,用戶定義的結(jié)構(gòu)體),枚舉,可空類型。

C#的引用類型包括:數(shù)組,用戶定義的類、接口、委托,object,字符串。

數(shù)組的元素,不管是引用類型還是值類型,都存儲在托管堆上。

引用類型在棧中存儲一個引用,其實際的存儲位置位于托管堆。為了方便,本文簡稱引用類型部署在托管推上。

值類型總是分配在它聲明的地方:作為字段時,跟隨其所屬的變量(實例)存儲;作為局部變量時,存儲在棧上。

值類型在內(nèi)存管理方面具有更好的效率,并且不支持多態(tài),適合用作存儲數(shù)據(jù)的載體;引用類型支持多態(tài),適合用于定義應(yīng)用程序的行為。

應(yīng)該盡可能地將值類型實現(xiàn)為具有【常量性】和【原子性】的類型。

應(yīng)該盡可能地確保0為值類型的有效狀態(tài)。

應(yīng)該盡可能地減少裝箱和拆箱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內(nèi)容