1. 程式人生 > >解讀經典《C#高階程式設計》第七版 Page32-38.核心C#.Chapter2

解讀經典《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型別是個非常特殊又奇怪的型別,它是引用型別,但它的行為又像值型別,這是特意設計的,因為字串的使用太普遍了,而值型別的概念比引用型別使用起來更直觀和簡單。它的幾個特點:

  1. String物件是引用型別,它被分配在堆上,而不是棧上
  2. 因此,當把一個字串變數賦予另一個字串時,會得到對記憶體中同一個字串的兩個引用
  3. 字串是不可改變的。修改其中一個字串,就會建立一個全新的String物件,而另一個字串不發生任何變化

有了以上特性,我們可以看到值型別的行為了:當賦予一個不同的字串值,系統總給你new一個字串新值;而當你賦值已有的某個字串時,系統從堆中給你找出來賦值給你。這樣既保證了值型別的行為,也優化了引用型別的效能,是一種平衡的好方案。當然在某些場景下,String的這種特性也會導致效能問題,比如在for迴圈中拼接一個字串,這個時候簡單的用賦值語句改變String時,就會導致一直建立新的字串,效能大降,所以這個時候我們常常會用StringBuilder來構造字串。

字串中,常碰到轉義符的問題,字串中的\將被理解為轉義符,怎麼讓C#不將其理解為轉移符呢?“將轉義符再轉義”,即再加\,比如一個目錄路徑表示為:

string path = “C:\\windows\\Temp”;

我們可以簡化這種寫法為:

string path = @“C:\windows\Temp”;

@不僅是用於描述路徑,還用於其他多種場景,這會讓人混淆,@有沒有可明確描述的效用?有的,它的功能可通用性的描述為:在這個@後的所有字元都看作是其原來的含義。比如上面的路徑例子,原來的含義就是\就是路徑的表述,而不是轉義符的意思。還有比如@用在字串換行中:

string path = @“華為是

中國的驕傲”;

當沒有@時,字串在輸入中換行會導致編譯錯誤,因為回車換行在語言規範中有特殊含義,加了@表示告訴編譯器,“我這字串中的換行符就是原本的換行的意思,你不用特殊解釋”。

其他所有用@的場景,都可以如此解釋,這是一種可通用的解釋。

 

預定義型別內容非常長,就先講到這裡,下一篇我們講流控制。

 

覺得文章有意義的話,請動動手指,分享給朋友一起來共同學習進步。

 

歡迎關注本人微信公眾號,更及時的關注最新文章(每週三篇原創文章,以及多篇專題文章):

附文:

C#的兩種類據型別:值型別和引用型別

C# String與StringBuilder

 

上一篇:解讀經典《C#高階程式設計》第七版 Page20-32.核心C#.Chapter2