天天看點

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

點選檢視第一章 點選檢視第二章

第3章

更多資料類型

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

第2章讨論了所有C#預定義類型,簡單提到了引用類型和值類型的差別。本章繼續讨論資料類型,深入解釋類型劃分。

此外,本章還要讨論将資料元素合并成元組的細節,這是C# 7.0引入的一個功能。最後讨論如何将資料分組到稱為數組的集合中。首先深入了解值類型和引用類型。

3.1 類型的劃分

一個類型要麼是值類型,要麼是引用類型。差別在于拷貝方式:值類型的資料總是拷貝值;而引用類型的資料總是拷貝引用。

3.1.1 值類型

除了string,本書目前講到的所有預定義類型都是值類型。值類型直接包含值。換言之,變量引用的位置就是記憶體中實際存儲值的位置。是以,将一個值賦給變量1,再将變量1賦給變量2,會在變量2的位置建立值的拷貝,而不是引用變量1的位置。這進一步造成更改變量1的值不會影響變量2的值。圖3.1對此進行了示範。number1引用記憶體中的特定位置,該位置包含值42。将number1的值賦給number2之後,兩個變量都包含值42。但修改其中任何一個值都不會影響另一個值。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

類似地,将值類型的執行個體傳給Console.WriteLine()這樣的方法也會生成記憶體拷貝。在方法内部對參數值進行的任何修改都不會影響調用函數中的原始值。由于值類型需要建立記憶體拷貝,是以定義時不要讓它們占用太多記憶體(通常應該小于16位元組)。

3.1.2 引用類型

相反,引用類型的變量存儲對資料存儲位置的引用,而不是直接存儲資料。要去那個位置才能找到真正的資料。是以為了通路資料,“運作時”要先從變量中讀取記憶體位置,再“跳轉”到包含資料的記憶體位置。為引用類型的變量配置設定實際資料的記憶體區域稱為堆(heap),如圖3.2所示。

引用類型不像值類型那樣要求建立資料的記憶體拷貝,是以拷貝引用類型的執行個體比拷貝大的值類型執行個體更高效。将引用類型的變量賦給另一個引用類型的變量,隻會拷貝引用而不需要拷貝所引用的資料。事實上,每個引用總是系統的“原生大小”:32位系統拷貝32位引用,64位系統拷貝64位引用,以此類推。顯然,拷貝對一個大資料塊的引用,比拷貝整個資料塊快得多。

由于引用類型隻拷貝對資料的引用,是以兩個不同的變量可引用相同的資料。如兩個變量引用同一個對象,利用一個變量更改對象的字段,用另一個對象通路字段将看到更改結果。無論指派還是方法調用都會如此。是以,如果在方法内部更改引用類型的資料,控制傳回調用者之後,将看到更改後的結果。有鑒于此,如對象在邏輯上是固定大小、不可變的值,就考慮定義成值類型。如邏輯上是可引用、可變的東西,就考慮定義成引用類型。

除了string和自定義類(如Program),本書目前講到的所有類型都是值類型。但大多數類型都是引用類型。雖然偶爾需要自定義的值類型,但更多的還是自定義的引用類型。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

3.2 可空修飾符

一般不能将null值賦給值類型。這是因為根據定義,值類型不能包含引用,即使是對“什麼都沒有(nothing)”的引用。但在值本來就缺失的時候,這也會帶來問題。例如在指定計數的時候,如計數未知,那麼應該輸入什麼?一個可能的解決方案是指定特殊值,比如-1或int.MaxValue,但這些都是有效整數。我們倒希望能将null賦給該變量,因為null不是有效整數。

為聲明能存儲null的變量,要使用可空修飾符?。代碼清單3.1示範了自C# 2.0引入的這個功能。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

将null賦給值類型,這在資料庫程式設計中尤其有用。在資料表中,經常出現值類型的列允許為空的情況。除非允許包含null值,否則在C#代碼中檢索這些列并将它們的值賦給對應字段會出問題。可空修飾符能妥善解決該問題。

隐式類型的局部變量

C# 3.0新增上下文關鍵字var來聲明隐式類型的局部變量。聲明變量時,如果能用确定類型的表達式初始化它,C# 3.0及以後的版本就允許變量的資料類型為“隐式的”,無須顯式聲明,如代碼清單3.2所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

上述代碼清單和代碼清單2.18相比有兩處不同。首先,不顯式聲明為string類型,而是聲明為var。最終的CIL代碼沒有差別。但var告訴編譯器根據聲明時所賦的值(System.Console.ReadLine())來推斷資料類型。

其次,text和uppercase變量都在聲明時初始化。不這樣做會造成編譯時錯誤。如前所述,編譯器判斷初始化表達式的資料類型并相應地聲明變量,就好像程式員顯式指定了類型。

雖然允許用var取代顯式資料類型,但在資料類型已知的情況下最好不要用var。例如,還是應該将text和uppercase聲明為string。這不僅可使代碼更易了解,還相當于你親自确認了等号右側表達式傳回的是你希望的資料類型。使用var變量時,右側資料類型應顯而易見,否則應避免用var聲明變量。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

進階主題:匿名類型

C# 3.0添加var的真正目的是支援匿名類型。匿名類型是在方法内部動态聲明的資料類型,而不是通過顯式的類定義來聲明,如代碼清單3.3所示。(第15章會深入讨論匿名類型。)

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

輸出3.1展示了結果。

輸出3.1

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

代碼清單3.3示範了如何将匿名類型的值賦給隐式類型(var)局部變量。C# 3.0支援連接配接(關聯)資料類型或将特定類型的大小縮減至更少資料元素,是以才配合設計了這種操作。但自從C# 7.0引入元組文法後,匿名類型幾乎就用不着了。

3.3 元組

有時需要合并資料元素。例如,2017年全球最貧窮的國家是首都位于Lilongwe(利隆圭)的Malawi(馬拉威),人均GDP 為226.50美元。利用目前講過的程式設計構造,可将上述每個資料元素存儲到單獨的變量中,但它們互相無關聯。換言之,看不出226.50和Malawi有什麼聯系。為解決該問題,第一個方案是在變量名中使用統一的字尾或字首,第二個方案是将所有資料合并到一個字元串中,但缺點是需要解析字元串才能處理單獨的資料元素。

C# 7.0提供了第三個方案:元組(tuple),允許在一個語句中完成所有變量的指派,如下所示:

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

表3.1總結了元組的其他文法形式。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

前四個例子雖然右側是元組,但左側仍然是單獨的變量,隻是用元組文法一起指派。在這種文法中,兩個或更多元素以逗号分隔,放到一對圓括号中進行組合。(我使用“元組文法”一詞是因為編譯器為左側生成的基礎資料類型技術上說并非元組。)結果是雖然右側的值合并成元組,但在向左側指派的過程中,元組已被解構為它的組成部分。例2左邊被指派的變量是事先聲明好的,但例1、3和4的變量是在元組文法中聲明的。由于隻是聲明變量,是以命名和大小寫應遵循第1章的設計規範,例如有一條是“要為局部變量使用camelCase風格命名。”

雖然隐式類型(var)在例4中用元組文法平均配置設定給每個變量聲明,但這裡的var絕不可以替換成顯式類型(如string)。元組宗旨是允許每一項都有不同資料類型,是以為每一項都指定同一個顯式類型名稱跟這個宗旨沖突(即使類型真的一樣,編譯器也不允許指定顯式類型)。

例5在左側聲明一個元組,将右側的元組賦給它。注意元組含具名項,随後可引用這些名稱來擷取右側元組中的值。這正是能在System.Console.WriteLine語句中使用countryInfo.Name、countryInfo.Capital和countryInfo.GdpPerCapita文法的原因。在左側聲明元組造成多個變量組合到單個元組變量(countryInfo)中。然後可利用元組變量來通路其組成部分。如第4章所述,這樣的設計允許将該元組變量傳給其他方法。那些方法能輕松通路元組中的項。

前面說過,用元組文法定義的變量應遵守camelCase大小寫規則。但該規則并未得到徹底貫徹。有人提倡當元組的行為和參數相似時(類似于元組文法出現之前用于傳回多個值的out參數),這些名稱應使用參數命名規則。

另一個方案是PascalCase大小寫,這是類型成員(屬性、函數和公共字段,參見第5章和第6章的讨論)的命名規範。個人強烈推薦PascalCase規範,進而和C#/.NET成員辨別符的大小寫規範一緻。但由于這并不是被廣泛接受的規範,是以我在設計規範“考慮為所有元組項名稱使用PascalCase大小寫風格”中使用“考慮”而非“要”一詞,

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

例6提供和例5一樣的功能,隻是右側元組使用了具名元組項,左側使用了隐式類型聲明。但元組項名稱會傳入隐式類型變量,是以WriteLine語句仍可使用它們。當然,左側可使用和右側不同的元組項名稱。C#編譯器允許這樣做但會顯示警告,指出右側元組項名稱會被忽略,因為此時左側的優先。不指定元組項名稱,被指派的元組變量中的單獨元素仍可通路,隻是名稱是Item1,Item2,...,如例7所示。事實上,即便提供了自定義名稱,ItemX名稱始終都能使用,如例8所示。但在使用Visual Studio這樣的IDE工具時,ItemX屬性不會出現在“智能感覺”的下拉清單中。這是好事,因為自己提供的名稱理論上應該更好。如例9所示,可用下劃線丢棄部分元組項的指派,這稱為棄元(discard)。

例10展示的元組項名稱推斷功能是自C# 7.1引入的。如本例所示,元組項名稱可根據變量名(甚至屬性名)來推斷。

元組是在對象中封裝資料的輕量級方案,有點像你用來裝雜貨的購物袋。和稍後讨論的數組不同,元組項的資料類型可以不一樣,沒有限制,隻是它們由編譯器決定,不能在運作時改變。另外,元組項數量也是在編譯時寫死好的。最後,不能為元組添加自定義行為(擴充方法不在此列)。如需和封裝資料關聯的行為,應使用面向對象程式設計并定義一個類,具體在第6章講述。

進階主題:System.ValueTuple<...>類型

在表3.1的示例中,C#為指派操作符右側的所有元組執行個體生成的代碼都基于一組泛型值類型(結構),例如System.ValueTuple。類似地,同一組System.ValueTuple<...>泛型值類型用于從例5開始的左側資料類型。元組類型唯一包含的方法是跟比較和相等性測試有關的方法,這符合預期。

既然自定義元組項名稱及其類型沒有包含在System.ValueTuple<...>定義中,為什麼每個自定義元組項名稱都好像是System.ValueTuple<...>類型的成員,并能以成員的形式通路呢?讓人(尤其是那些熟悉匿名類型實作的人)驚訝的是,編譯器根本沒有為那些和自定義名稱對應的“成員”生成底層CIL代碼,但從C#的角度看,又似乎存在這樣的成員。

對于表3.1的所有具名元組例子,編譯器在元組剩下的作用域中顯然知道那些名稱。事實上,編譯器(和IDE)正是依賴該作用域通過項的名稱來通路它們。換言之,編譯器查找元組聲明中的項名稱,并允許代碼通路還在作用域中的項。也正是因為這一點,IDE的“智能感覺”不顯示底層的ItemX成員。它們會被忽略,替換成顯式命名的項。

編譯器能判斷作用域中的元組項名稱,這一點還好了解,但如果元組要對外公開,比如作為另一個程式集中的一個方法的參數或傳回值使用(另一個程式集可能看不到你的源代碼),那麼會發生什麼?其實對于作為API(公共或私有)一部分的所有元組,編譯器都會以“特性”(attribute)的形式将元組項名稱添加到成員中繼資料中。例如,代碼清單3.4展示了編譯器為以下方法生成的CIL代碼的C#形式:

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

另外要注意,如顯式使用System.ValueTuple<...>類型,C#就不允許使用自定義的元組項名稱。是以表3.1的例8如果将var替換成該類型,編譯器會警告所有項的名稱被忽略。

下面總結了和System.ValueTuple<...>有關的其他注意事項:

  • 共有8個泛型System.ValueTuple<...>。前7個最大支援七元組。第8個是System.ValueTuple,可為最後一個類型參數指定另一個ValueTuple,進而支援n元組。例如,編譯器自動為8個參數的元組生成System.ValueTuple>作為底層實作類型。System.Value的存在隻是為了補全,很少使用,因為C#元組文法要求至少兩項。
  • 有一個非泛型System.ValueTuple類型作為元組工廠使用,提供了和所有ValueTuple元數對應的Create()方法。C# 7.0以後基本用不着Create()方法,因為像var t1 = ("Inigo Montoya", 42)這樣的元組字面值實在太好用了。

C#程式員實際程式設計時完全可以忽略System.ValueTuple和System.ValueTuple。

還有一個元組類型是Microsoft .NET Framework 4.5引入的System.Tuple<...>。當時是想把它打造成核心元組實作。但在C#中引入元組文法時才意識到值類型更佳,是以量身定制了System.ValueTuple<...>,它在所有情況下都代替了System.Tuple<...>(除非要向後相容依賴System.Tuple<...>的遺留API)。

3.4 數組

第1章沒有提到的一種特殊的變量聲明就是數組聲明。利用數組聲明,可在單個變量中存儲同一種類型的多個資料項,而且可利用索引來單獨通路這些資料項。C#的數組索引從零開始,是以我們說C#數組基于零。

初學者主題:數組

可用數組變量聲明同類型多個資料項的集合。每一項都用名為索引的整數值進行唯一性辨別。C#數組的第一個資料項使用索引0通路。由于索引基于零,應確定最大索引值比數組中的資料項總數小1。

初學者可将索引想象成偏移量。第一項距數組開頭的偏移量是0,第二項偏移量是1,以此類推。

數組是幾乎所有程式設計語言的基本組成部分,所有開發人員都應學習。雖然C#程式設計經常用到數組,初學者也确實應該掌握,但大多數程式現在都用泛型集合類型而非數組來存儲資料集合。如隻是為了熟悉數組的執行個體化和指派,可略讀下一節。表3.2列出了要注意的重點。泛型集合在第15章詳細講述。

此外,3.4.5節還會講到數組的一些特點。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

3.4.1 數組的聲明

C#用方括号聲明數組變量。首先指定數組元素的類型,後跟一對方括号,再輸入變量名。代碼清單3.5聲明字元串數組變量languages。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

顯然,數組聲明的第一部分辨別了數組中存儲的元素的類型。作為聲明的一部分,方括号指定了數組的秩(rank),或者說維數。本例聲明一維數組。類型和維數構成了languages變量的資料類型。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

代碼清單3.5定義的是一維數組。方括号中的逗号用于定義額外的維。例如,代碼清單3.6為井字棋(tic-tac-toe)棋盤定義了一個二維數組。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

代碼清單3.6定義了一個二維數組。第一維對應從左到右的單元格,第二維對應從上到下的單元格。可用更多逗号定義更多元,數組總維數等于逗号數加1。注意某一維上的元素數量不是變量聲明的一部分。這是在建立(執行個體化)數組并為每個元素配置設定記憶體空間時指定的。

3.4.2 數組執行個體化和指派

聲明數組後,可在一對大括号中使用以逗号分隔的資料項清單來填充它的值。代碼清單3.7聲明一個字元串數組,将一對大括号中的9種語言名稱賦給它。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

清單第一項成為數組的第一個元素,第二項成為第二個,以此類推。我們用大括号定義數組字面值。

隻有在同一個語句中聲明并指派,才能使用代碼清單3.7的指派文法。聲明後在其他地方指派則需使用new關鍵字,如代碼清單3.8所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

自C# 3.0起不必在new後指定數組類型(string)。編譯器能根據初始化清單中的資料類型推斷數組類型。但方括号仍不可缺少。

C#支援将new關鍵字作為聲明語句的一部分,是以可以像代碼清單3.9那樣在聲明時指派。

new關鍵字的作用是訓示“運作時”為資料類型配置設定記憶體,即訓示它執行個體化資料類型(本例是數組)。

數組指派時隻要使用了new關鍵字,就可在方括号内指定數組大小,如代碼清單3.10所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

指定的數組大小必須和大括号中的元素數量比對。另外,也可配置設定數組但不提供初始值,如代碼清單3.11所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

配置設定數組但不指定初始值,“運作時”會将每個元素初始化為它們的預設值,如下所示:

  • 引用類型(比如string)初始化為null;
  • 數值類型初始化為0;
  • bool初始化為false;
  • char初始化為0。

非基元值類型以遞歸方式初始化,每個字段都被初始化為預設值。是以,其實并不需要在使用數組前初始化它的所有元素。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

由于數組大小不需要作為變量聲明的一部分,是以可以在運作時指定數組大小。例如,代碼清單3.12根據在Console.ReadLine()調用中使用者指定的大小建立數組。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

C#以類似的方式處理多元數組。每一維的大小以逗号分隔。代碼清單3.13初始化一個沒有開始走棋的井字棋棋盤。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

還可以像代碼清單3.14那樣,将井字棋棋盤初始化成特定的棋子布局。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

數組包含三個int[]類型的元素,每個元素大小一樣(本例中湊巧也是3)。注意每個int[]元素的大小必須完全一樣。也就是說,像代碼清單3.15那樣的聲明是無效的。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

表示棋盤并不需要在每個位置都使用整數。另一個辦法是為每個玩家都單獨提供虛拟棋盤,每個棋盤都包含一個bool來指出玩家選擇的位置。代碼清單3.16對應于一個三維棋盤。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

本例初始化棋盤并顯式指定每一維的大小。new表達式除了指定大小,還提供了數組的字面值。bool[,,]類型的字面值被分解成兩個bool[,]類型的二維數組(大小均為3×3)。每個二維數組都由三個bool數組(大小為3)構成。

如前所述,多元數組(這種普通多元數組也稱為“矩形數組”)每一維的大小必須一緻。還可定義交錯數組(jagged array),也就是由數組構成的數組。交錯數組的文法稍微有别于多元數組,而且交錯數組不需要具有一緻的大小。是以,可以像代碼清單3.17那樣初始化交錯數組。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

交錯數組不用逗号辨別新維。相反,交錯數組定義由數組構成的數組。代碼清單3.17在int[]後添加[],表明數組元素是int[]類型的數組。

注意,交錯數組要求為内部的每個數組都建立數組執行個體。這個例子使用new執行個體化交錯數組的内部元素。遺失這個執行個體化部分會造成編譯時錯誤。

3.4.3 數組的使用

使用方括号(稱為數組通路符)通路數組元素。為擷取第一個元素,要指定0作為索引。代碼清單3.18将languages變量中的第5個元素(索引4)的值存儲到變量language中。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

還可用方括号文法将資料存儲到數組中。代碼清單3.19交換了"C++"和"Java"的順序。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

多元數組的元素用每一個維的索引來辨別,如代碼清單3.20所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

交錯數組元素的指派稍有不同,這是因為它必須與交錯數組的聲明一緻。第一個索引指定“由數組構成的數組”中的一個數組。第二個索引指定是該數組中的哪一項(參見代碼清單3.21)。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

1. 長度

像代碼清單3.22那樣擷取數組長度。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

數組長度固定,除非重新建立數組,否則不能随便更改。此外,越過數組的邊界(或長度)會造成“運作時”報錯。用無效索引(指向的元素不存在)來通路(檢索或者指派)數組時就會發生這種情況。例如在代碼清單3.23中,用數組長度作為索引來通路數組就會出錯。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

一個好的實踐是用Length取代寫死的數組大小。例如,代碼清單3.24修改了上個代碼清單,在索引中使用了Length(減1獲得最後一個元素的索引)。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

為避免越界,應使用長度檢查來驗證數組長度大于0。通路數組最後一項時,要像代碼清單3.24那樣使用Length-1而不是寫死的值。

Length傳回數組中元素的總數。是以,如果你有一個多元數組,比如大小為2×3×3的bool cells[,,]數組,那麼Length會傳回元素總數18。

對于交錯數組,Length傳回外部數組的元素數—交錯數組是“數組構成的數組”,是以Length隻作用于外部數組,隻統計它的元素數(也就是具體由多少個數組構成),而不管各内部數組共包含了多少個元素。

2. 更多數組方法

數組提供了更多方法來操作數組中的元素,其中包括Sort()、BinarySearch()、Reverse()和Clear()等,如代碼清單3.25所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

輸出3.2展示了結果。

輸出3.2

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

這些方法通過System.Array類提供。大多數都一目了然,但注意以下兩點。

  • 使用BinarySearch()方法前要先對數組進行排序。如果值不按升序排序,會傳回不正确的索引。目标元素不存在會傳回負值,在這種情況下,可應用按位求補運算符~index傳回比目标元素大的第一個元素的索引(如果有的話)。
  • Clear()方法不删除數組元素,不将長度設為零。數組大小固定,不能修改。是以Clear()方法将每個元素都設為其預設值(false、0或null)。這解釋了在調用Clear()之後輸出數組時,Console.WriteLine()為什麼會建立一個空行。
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

3. 數組執行個體方法

類似于字元串,數組也有不從資料類型而是從變量通路的執行個體成員。Length就是一個例子,它通過數組變量來通路,而非通過類。其他常用執行個體成員還有GetLength()、Rank和Clone()。

擷取特定維的長度不是用Length屬性,而是用數組的GetLength()執行個體方法,調用時需指定傳回哪一維的長度,如代碼清單3.26所示。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

結果如輸出3.3所示。

輸出3.3

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

輸出2,這是第一維的元素個數。

還可通路數組的Rank成員擷取整個數組的維數。例如,cells.Rank傳回3。

将一個數組變量賦給另一個預設隻拷貝數組引用,而不是數組中單獨的元素。要建立數組的全新拷貝需使用數組的Clone()方法。該方法傳回數組拷貝,修改新數組不會影響原始數組。

3.4.4 字元串作為數組使用

通路string類型的變量類似于通路字元數組。例如,可調用palindrome[3]擷取palindrome字元串的第4個字元。注意由于字元串不可變,是以不能向字元串中的特定位置指派。是以,對于palindrome字元串來說,在C#中不允許string,palindrome[3]='a'這樣的寫法。代碼清單3.27使用數組通路符判斷指令行上的參數是不是選項(選項的第一個字元是短劃線)。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

上述代碼使用了要在第4章講述的if語句。注意第一個數組通路符[]擷取字元串數組args的第一個元素,第二個數組通路符則擷取該字元串的第一個字元。上述代碼等價于代碼清單3.28。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

不僅可用數組通路符單獨通路字元串中的字元,還可使用字元串的ToCharArray()方法将整個字元串作為字元數組傳回,再用System.Array.Reverse()方法反轉數組中的元素,如代碼清單3.29所示,該程式判斷字元串是不是回文。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

輸出3.4展示了結果。

輸出3.4

帶你讀《C# 7.0本質論》之三:更多資料類型第3章

這個例子使用new關鍵字根據反轉好的字元數組建立新字元串。

3.4.5 常見數組錯誤

前面描述了三種不同類型的數組:一維、多元和交錯。一些規則和特點限制着數組的聲明和使用。表3.3總結了一些常見錯誤,有助于鞏固對這些規則的了解。閱讀時最好先看“常見錯誤”一欄的代碼(先不要看錯誤說明和改正後的代碼),看自己是否能發現錯誤,檢查你對數組及其文法的了解。

帶你讀《C# 7.0本質論》之三:更多資料類型第3章
帶你讀《C# 7.0本質論》之三:更多資料類型第3章

3.5 小結

本章首先讨論了兩種不同的類型:值類型和引用類型。它們是C#程式員必須了解的基本概念,雖然讀代碼時可能看不太出來,但它們改變了類型的底層機制。

讨論數組前先讨論了兩種語言構造。首先讨論了C# 2.0引入的可空修飾符(?),它允許值類型存儲空值。然後讨論了元組,并介紹如何用C# 7.0引入的新文法處理元組,同時不必顯式地和底層資料類型打交道。

最後讨論了C#數組文法,并介紹了各種數組處理方式。許多開發者剛開始不容易熟練掌握這些文法。是以提供了一個常見錯誤清單,專門列出與數組編碼有關的錯誤。

下一章讨論表達式和控制流程語句。本章最後出現過幾次的if語句會一并讨論。

繼續閱讀