解讀經典《C#高階程式設計》第七版 Page32-38.核心C#.Chapter2
前言
接下來講講預定義資料型別。關於資料型別,其實是非常值得透徹研究的。
01
預定義資料型別
值型別和引用型別
C#將把資料型別分為兩種,值型別和引用型別,值型別儲存在堆疊上,引用型別儲存在託管堆上。因此,對於值型別,如果:
Int a = 1;
Int b = a;
那麼記憶體中就有兩份的值1。
而對應引用型別,如果:
User userA = new User();
User userB = userA;
那麼記憶體中只有一份User物件,userA和userB都指向它。
需要提到一句的是,結構(struct)是值型別,雖然我從來沒有用到,但在需要極致優化效能的時候,可能是用的上的。
這基本上是大家都知道的。但在實際使用中,這種所謂的引用關係會比較隱祕,造成我們使用引用型別上的一些問題,所以我們需要更深入的講一講。
CTS型別(Common Type System)
CTS型別是.Net Framework的型別,而不是C#的型別,即我們之前講到,CLR也是高度物件化的。在CLR層面規定了通用的型別,這樣不同的開發語言比如C#,VB.Net才能良好的融合。比如Int32,Int64就是.Net框架的結構。我們定義一個Int和long既可以寫為:
int val = 10;
long val2 1000;
也可以寫為:
Int32 val = 10;
Int64 val2 = 1000;
當然我們實際使用中不會像後者那麼用。
我們還可以發現,Int32,Int64是結構(struct),是值型別,所以這種型別通用化對.Net框架的效能的影響極小。
預定義的值型別
預定義的值型別有:
整數:
浮點數:
基本型別在賦值時,可以使用字尾(可大寫或者小寫)明確指定型別,比如:
Long val1 = 10L;
float val2 = 12.3F;
對於整數型別,如果不明確指定型別,系統會預設為int,而對於浮點數型別,不明確指定時系統預設為double。
我曾經很疑惑的是,既然這麼說,是不是在初始化的時候一定要這麼指定字尾呢?畢竟初始化基本型別是比較常見的操作。而我現在認為,非特殊情況下,基本上是不需要的
Long val1 = 10L; 和 Long val1 = 10;
效果是一樣的。因為val1定義為long,作為int的10會自動向上轉型為long,不會有精度損失。而對於:
float val2 = 12.3F; 和float val2 = 12.3; 有沒有區別呢?是有的,因為後面這句賦值語句有錯誤,不能通過編譯,因為作為double的12.3轉化為float,是有精度損失的,所以要麼你強制轉換,要麼你加F。
所以我們會發現,你寫一個對基礎型別的賦值語句,你能寫出來,且編譯器沒提示錯誤,就是沒問題的;不能寫的,編譯器會提醒你。編譯器已經足夠智慧,不需要擔心要不要加字尾的問題。
Decimal型別:
Decimal是個很重要的型別,我在實際專案中,從來沒有用過float和double,因為它們有精度問題,我都是要麼整數(用於數量),要麼Decimal(用於價格,金額等)。.Net把它稱為“用於財務計算的專用型別”。我們對比Java會發現,Java的高精度計算型別是一個叫做BigDecimal的普通類,它的初始化要用比如new BigDecimal(10)這種類建立的方式來實現。而在C#中將其地位提升為預定義型別。但同時,書中也強調,Decimal仍然不是“基本型別”,我們可以認為,它的本質和Java的BigDecimal是一樣的,只是將其“模擬”為基本型別,這樣使用更便捷,但它的計算實際上仍然會有效能損失。
Bool型別:
字元型別:
字元型別,有多種表示方式:
char val1 = 'A'; //字面量
char val2 = '\u0041'; //Unicode
char val3 = (char)65; //int轉換
char val4 = '\x0041'; //16進位制
字元常用的還有用“轉義符”(反斜槓\)表示的特殊字元,比如換行符\n,回車符\r等。
預定義的引用型別
預定義引用型別的概念太重要了,因為它是.Net整個類結構的基礎。
上圖的每個字都值得分析。我們注意到,object型別被叫做“根型別”,它是包括值型別的所有其他型別的父型別。為什麼值型別也是?因為值物件可以被“裝箱”並放入托管堆而成為引用物件,而裝箱又導致拆箱,這是非常重要的概念,對這個概念理解的不好,會寫出意想不到的複雜程式碼。
而String型別的概念是:Unicode字串,也很重要,這說明什麼,說明字元亂碼問題得到了徹底解決,一個英文和一箇中文字元長度相同,等等。String型別是個非常特殊又奇怪的型別,它是引用型別,但它的行為又像值型別,這是特意設計的,因為字串的使用太普遍了,而值型別的概念比引用型別使用起來更直觀和簡單。它的幾個特點:
- String物件是引用型別,它被分配在堆上,而不是棧上
- 因此,當把一個字串變數賦予另一個字串時,會得到對記憶體中同一個字串的兩個引用
- 字串是不可改變的。修改其中一個字串,就會建立一個全新的String物件,而另一個字串不發生任何變化
有了以上特性,我們可以看到值型別的行為了:當賦予一個不同的字串值,系統總給你new一個字串新值;而當你賦值已有的某個字串時,系統從堆中給你找出來賦值給你。這樣既保證了值型別的行為,也優化了引用型別的效能,是一種平衡的好方案。當然在某些場景下,String的這種特性也會導致效能問題,比如在for迴圈中拼接一個字串,這個時候簡單的用賦值語句改變String時,就會導致一直建立新的字串,效能大降,所以這個時候我們常常會用StringBuilder來構造字串。
字串中,常碰到轉義符的問題,字串中的\將被理解為轉義符,怎麼讓C#不將其理解為轉移符呢?“將轉義符再轉義”,即再加\,比如一個目錄路徑表示為:
string path = “C:\\windows\\Temp”;
我們可以簡化這種寫法為:
string path = @“C:\windows\Temp”;
@不僅是用於描述路徑,還用於其他多種場景,這會讓人混淆,@有沒有可明確描述的效用?有的,它的功能可通用性的描述為:在這個@後的所有字元都看作是其原來的含義。比如上面的路徑例子,原來的含義就是\就是路徑的表述,而不是轉義符的意思。還有比如@用在字串換行中:
string path = @“華為是
中國的驕傲”;
當沒有@時,字串在輸入中換行會導致編譯錯誤,因為回車換行在語言規範中有特殊含義,加了@表示告訴編譯器,“我這字串中的換行符就是原本的換行的意思,你不用特殊解釋”。
其他所有用@的場景,都可以如此解釋,這是一種可通用的解釋。
預定義型別內容非常長,就先講到這裡,下一篇我們講流控制。
覺得文章有意義的話,請動動手指,分享給朋友一起來共同學習進步。
歡迎關注本人微信公眾號,更及時的關注最新文章(每週三篇原創文章,以及多篇專題文章):
附文: