- C#入門經典(第7版):C# 6.0 & Visual Studio 2015(.NET開發經典名著)
- (美)Beijamin Perkins Jacob Vibe Hammer Jon D. Reid
- 5413字
- 2021-04-02 21:18:36
5.2 復雜的變量類型
除了這些簡單的變量類型外,C#還提供了3個較復雜(但非常有用)的變量:枚舉、結構和數組。
5.2.1 枚舉
本書迄今介紹的每種類型(除string外)都有明確的取值范圍。誠然,有些類型(如double)的取值范圍非常大,可以看成是連續的,但它們仍是一個固定集合。最簡單的示例是bool類型,它只能取兩個值:true或false。
有時希望變量取的是一個固定集合中的值。例如,讓orientation類型可以存儲north、south、east或west中的一個值。
此時可以使用枚舉類型。枚舉可以完成這個orientation類型的任務:它們允許定義一個類型,其取值范圍是用戶提供的值的有限集合。所以,需要創建自己的枚舉類型orientation,它可以從上述4個值中取一個值。
注意有一個附加步驟—— 不是僅聲明一個給定類型的變量,而是聲明和描述一個用戶定義的類型,再聲明這個新類型的變量。
定義枚舉
可以用enum關鍵字定義枚舉,如下所示:
enum <typeName> { <value1>, <value2>, <value3>, ... <valueN> }
接著聲明這個新類型的變量:
<typeName> <varName>;
并賦值:
<varName> = <typeName>.<value>;
枚舉使用一個基本類型來存儲。枚舉類型可取的每個值都存儲為該基本類型的一個值,默認情況下該類型為int。在枚舉聲明中添加類型,就可以指定其他基本類型:
enum <typeName> : <underlyingType> { <value1>, <value2>, <value3>, ... <valueN> }
枚舉的基本類型可以是byte、sbyte、short、ushort、int、uint、long和ulong。
默認情況下,每個值都會根據定義的順序(從0開始),被自動賦予對應的基本類型值。這意味著<value1>的值是0, <value2>的值是1, <value3>的值是2,等等。可以重寫這個賦值過程:使用=運算符,指定每個枚舉的實際值:
enum <typeName> : <underlyingType> { <value1> = <actualVal1>, <value2> = <actualVal2>, <value3> = <actualVal3>, ... <valueN> = <actualValN> }
還可以使用一個值作為另一個枚舉的基礎值,為多個枚舉指定相同的值:
enum <typeName> : <underlyingType> { <value1> = <actualVal1>, <value2> = <value1>, <value3>, ... <valueN> = <actualValN> }
未賦值的任何值都會自動獲得一個初始值,這里使用的值是從比上一個明確聲明的值大1開始的序列。例如,在上面的代碼中,<value3>的值是<value1> + 1。
注意這可能會產生預料不到的問題,在一個定義(如<value2> = <value1>)后指定的值可能與其他值相同。例如,在下面的代碼中,<value4>的值與<value2>的值相同:
enum <typeName> : <underlyingType> { <value1> = <actualVal1>, <value2>, <value3> = <value1>, <value4>, ... <valueN> = <actualValN> }
當然,如果這正是希望的結果,代碼就是正確的。還要注意,以循環方式賦值可能會產生錯誤,例如:
enum <typeName> : <underlyingType> { <value1> = <value2>, <value2> = <value1> }
下面看一個示例。其代碼定義了一個枚舉orientation,然后演示了它的用法。
試一試:使用枚舉:Ch05Ex02\Program.cs
(1)在C:\BegVCSharp\Chapter05目錄中創建一個新的控制臺應用程序Ch05Ex02。
(2)把下列代碼添加到Program.cs中:
namespace Ch05Ex02 { enum orientation : byte { north = 1, south = 2, east = 3, west = 4 } class Program { static void Main(string[] args) { orientation myDirection = orientation.north; WriteLine($"myDirection = {myDirection}"); ReadKey(); } } }
(3)運行應用程序,應得到如圖5-5所示的輸出結果。

圖5-5
(4)退出應用程序,修改代碼,如下所示:
byte directionByte; string directionString; orientation myDirection = orientation.north; WriteLine($"myDirection = {myDirection}"); directionByte = (byte)myDirection; directionString = Convert.ToString(myDirection); WriteLine($"byte equivalent = {directionByte}"); WriteLine($"string equivalent = {directionString}"); ReadKey();
(5)再次運行應用程序,輸出結果如圖5-6所示。

圖5-6
示例說明
這段代碼定義并使用了一個枚舉類型orientation。首先要注意,類型定義代碼放在名稱空間Ch05Ex02中,但沒有與其余代碼放在一起。這是因為在運行期間,定義代碼并不像執行應用程序中的代碼那樣一行一行地執行。應用程序是從我們熟悉的位置開始執行的,它可以訪問新類型,因為該類型位于同一個名稱空間中。
這個示例的第一個迭代演示了創建新類型的變量,給它賦值以及把它輸出到屏幕上的基本方法。接著修改代碼,把枚舉值轉換為其他類型。注意這里必須使用顯式轉換。即使orientation的基本類型是byte,也仍必須使用(byte)強制實現類型轉換,把myDirection的值轉換為byte類型:
directionByte = (byte)myDirection;
如果要將byte類型轉換為orientation,也同樣需要進行顯式轉換。例如,可以使用下述代碼將byte變量myByte轉換為orientation值,并將這個值賦給myDirection:
myDirection = (orientation)myByte;
當然,這里必須小心,因為并不是所有byte類型變量的值都可以映射為已定義的orientation值。orientation類型可以存儲其他byte值,所以這么做不會直接產生一個錯誤,但會在應用程序的后面違反邏輯。
要獲得枚舉的字符串值,可以使用Convert.ToString():
directionString = Convert.ToString(myDirection);
使用(string)強制類型轉換是行不通的,因為需要進行的處理并不僅是把存儲在枚舉變量中的數據放在string變量中,而是更復雜一些。另外,可以使用變量本身的ToString()命令。下面的代碼與使用Convert.ToString()的效果相同:
directionString = myDirection.ToString();
也可以把string轉換為枚舉值,但其語法稍復雜一些。有一個特定命令用于完成此類轉換,即Enum.Parse(),其用法如下:
(enumerationType)Enum.Parse(typeof(enumerationType), enumerationValueString);
這里使用了另一個運算符typeof,它可以得到操作數的類型。對orientation類型使用這個命令,如下所示:
string myString = "north"; orientation myDirection = (orientation)Enum.Parse(typeof(orientation), myString);
當然,并非所有字符串值都會映射為一個orientation值。如果傳送的一個值不能映射為枚舉值中的一個,就會產生錯誤。與C#中的其他值一樣,這些值是區分大小寫的,所以如果字符串與一個值相同,但大小寫不同(例如,將myString設置為North而不是north),就會產生錯誤。
5.2.2 結構
下一個要介紹的變量類型是結構(struct, structure的簡寫)。結構就是由幾個數據組成的數據結構,這些數據可能具有不同的類型。根據這個結構,可以定義自己的變量類型。例如,假定要存儲從起點開始到某一位置的路徑,路徑由方向和距離值(英里)組成。為簡單起見,假定該方向是指南針上的一點(這樣,方向就可以用上一節的orientation枚舉來表示),距離值可以用double類型來表示。
通過前面的代碼,可用兩個不同的變量來表示路徑:
orientation myDirection; double myDistance;
像這樣使用兩個變量,是沒有錯誤的,但在一個地方存儲這些信息更加簡單(在需要多個路徑時,就尤為簡單)。
定義結構
使用struct關鍵字定義結構,如下所示:
struct <typeName> { <memberDeclarations> }
<memberDeclarations>部分包含變量的聲明(稱為結構的數據成員),其格式與前面的變量聲明一樣。每個成員的聲明都采用如下形式:
<accessibility> <type> <name>;
要讓調用結構的代碼訪問該結構的數據成員,可以對<accessibility>使用關鍵字public,例如:
struct route { public orientation direction; public double distance; }
定義結構類型后,就可以定義該結構類型的變量:
route myRoute;
還可以通過句點字符訪問這個組合變量中的數據成員:
myRoute.direction = orientation.north; myRoute.distance = 2.5;
把這個類型放在下面的“試一試”示例中,其中使用上一個“試一試”示例中的orientation枚舉和上面的route結構。本例在代碼中處理這個結構,以便了解結構的工作原理。
試一試:使用結構:Ch05Ex03\Program.cs
(1)在C:\BegVCSharp\Chapter05目錄中創建一個新的控制臺應用程序Ch05Ex03。
(2)將下列代碼添加到Program.cs中:
namespace Ch05Ex03 { enum orientation: byte { north = 1, south = 2, east = 3, west = 4 } struct route { public orientation direction; public double distance; } class Program { static void Main(string[] args) { route myRoute; int myDirection = -1; double myDistance; WriteLine("1) North\n2) South\n3) East\n4) West"); do { WriteLine("Select a direction:"); myDirection = ToInt32(ReadLine()); } while ((myDirection < 1) || (myDirection > 4)); WriteLine("Input a distance:"); myDistance = ToDouble(ReadLine()); myRoute.direction = (orientation)myDirection; myRoute.distance = myDistance; WriteLine($"myRoute specifies a direction of {myRoute.direction} " + $"and a distance of {myRoute.distance}"); ReadKey(); } } }
(3)執行代碼,輸入一個介于1和4之間的數字,以選擇一個方向,輸入一個距離值,結果如圖5-7所示。

圖5-7
示例說明
結構和枚舉一樣,也是在代碼的主體之外聲明的。在名稱空間聲明中聲明route結構及其使用的orientation枚舉:
enum orientation: byte { north= 1, south= 2, east = 3, west = 4 } struct route { public orientation direction; public double distance; }
代碼的主體結構與前面的一些示例代碼類似,要求用戶輸入一些信息,并顯示它們。把方向選擇放在do循環中,對用戶的輸入進行有效性檢查,拒絕不屬于1-4范圍的整數輸入(選擇該范圍中的值可以映射到枚舉成員,從而方便賦值)。
注意:不能解釋為整數的輸入會導致一個錯誤。本章后面會說明其原因和處理方法。
注意,在引用route的成員時,處理它們的方式與處理成員類型相同的變量完全一樣。賦值語句如下所示:
myRoute.direction = (orientation)myDirection; myRoute.distance = myDistance;
可直接把輸入的值放到myRoute.distance中,而不會有負面效果,如下所示:
myRoute.distance = ToDouble(ReadLine());
還應進行有效性驗證,但這段代碼不存在這一步驟。對結構成員的任何訪問都以相同的方式處理。<structVar>.<memberVar>形式的表達式可計算<memberVar>類型的變量。
5.2.3 數組
前面的所有類型有一個共同點:它們都只存儲一個值(結構中存儲一組值)。有時,需要存儲許多數據,這樣就會帶來不便。有時需要同時存儲幾個類型相同的值,而不想為每個值使用不同的變量。
例如,假定要對所有朋友的姓名執行一些操作。可以使用簡單的字符串變量,如下所示:
string friendName1 = "Todd Anthony"; string friendName2 = "Kevin Holton"; string friendName3 = "Shane Laigle";
但這看起來需要做很多工作,特別是需要編寫不同的代碼來處理每個變量。例如,不能在循環中迭代這個字符串列表。
另一種方式是使用數組。數組是一個變量的索引列表,存儲在數組類型的變量中。例如,有一個數組friendNames存儲上述3個名字。在方括號中指定索引,即可訪問該數組中的各個成員,如下所示:
friendNames[<index>]
這個索引是一個整數,第一個條目的索引是0,第二個條目的索引是1,依此類推。這樣就可以使用循環遍歷所有條目,例如:
int i; for (i = 0; i < 3; i++) { WriteLine($"Name with index of {i}: {friendNames[i]}"); }
數組有一個基本類型,數組中的各個條目都是這種類型。friendNames數組的基本類型是字符串,因為它要存儲string變量。數組的條目通常稱為元素。
1.聲明數組
以下述方式聲明數組:
<baseType>[] <name>;
其中,<baseType>可以是任何變量類型,包括本章前面介紹的枚舉和結構類型。數組必須在訪問之前初始化,不能像下面這樣訪問數組或給數組元素賦值:
int[] myIntArray; myIntArray[10] = 5;
數組的初始化有兩種方式。可以字面值形式指定數組的完整內容,也可以指定數組的大小,再使用關鍵字new初始化所有數組元素。
要使用字面值指定數組,只需提供一個用逗號分隔的元素值列表,該列表放在花括號中,例如:
int[] myIntArray = { 5, 9, 10, 2, 99 };
其中,myIntArray有5個元素,每個元素都被賦予一個整數值。
另一種方式需要使用下述語法:
int[] myIntArray = new int[5];
這里使用關鍵字new顯式地初始化數組,用一個常量值定義其大小。這種方式會給所有數組元素賦予同一個默認值,對于數值類型來說,其默認值是0。也可以使用非常量的變量來進行初始化,例如:
int[] myIntArray = new int[arraySize];
還可以組合使用這兩種初始化方式:
int[] myIntArray = new int[5] { 5, 9, 10, 2, 99 };
使用這種方式,數組大小必須與元素個數相匹配。例如,不能編寫如下代碼:
int[] myIntArray = new int[10] { 5, 9, 10, 2, 99 };
其中數組定義為有10個元素,但只定義了5個元素,所以編譯會失敗。如果使用變量定義其大小,該變量必須是一個常量,例如:
const int arraySize = 5; int[] myIntArray = new int[arraySize] { 5, 9, 10, 2, 99 };
如果省略了關鍵字const,運行這段代碼就會失敗。
與其他變量類型一樣,并非必須在聲明數組的代碼行中初始化該數組。下面的代碼是合法的:
int[] myIntArray; myIntArray = new int[5];
下面的“試一試”示例利用了本節開頭的示例,創建并使用一個字符串數組。
試一試:使用數組:Ch05Ex04\Program.cs
(1)在C:\BegVCSharp\Chapter05目錄中創建一個新的控制臺應用程序Ch05Ex04。
(2)將下列代碼添加到Program.cs中:
static void Main(string[] args) { string[] friendNames = { "Todd Anthony", "Kevin Holton", "Shane Laigle" }; int i; WriteLine($"Here are {friendNames.Length} of my friends:"); for (i = 0; i < friendNames.Length; i++) { WriteLine(friendNames[i]); } ReadKey(); }
(3)執行代碼,結果如圖5-8所示。

圖5-8
示例說明
這段代碼用3個值建立了一個string數組,并在for循環中把它們列在控制臺上。使用friendNames.Length來確定數組中的元素個數:
WriteLine($"Here are {friendNames.Length} of my friends:");
這是獲取數組大小的簡便方法。在for循環中輸出值容易出錯。例如,把<改為<=,如下所示:
for (i = 0; i <= friendNames.Length; i++) { WriteLine(friendNames[i]); }
編譯并執行上述代碼,就會彈出如圖5-9所示的對話框。

圖5-9
這里,代碼試圖訪問friendNames[3]。記住,數組索引從0開始,所以最后一個元素是friendNames[2]。如果試圖訪問超出數組大小的元素,代碼就會出問題。還可以通過一個更具彈性的方法來訪問數組的所有成員,即使用foreach循環。
2. foreach循環
foreach循環可以使用一種簡便的語法來定位數組中的每個元素:
foreach (<baseType> <name> in <array>) { // can use <name> for each element }
這個循環會迭代每個元素,依次把每個元素放在變量<name>中,且不存在訪問非法元素的危險。不需要考慮數組中有多少個元素,并可以確保將在循環中使用每個元素。使用這個循環,可以修改上個示例中的代碼,如下所示:
static void Main(string[] args)
{
string[] friendNames = { "Todd Anthony", "Kevin Holton",
"Shane Laigle" };
WriteLine($"Here are {friendNames.Length} of my friends:");
foreach (string friendName in friendNames)
{
WriteLine(friendName);
}
ReadKey();
}
這段代碼的輸出結果與前面的“試一試”示例完全相同。使用這種方法和標準的for循環的主要區別在于:foreach循環對數組內容進行只讀訪問,所以不能改變任何元素的值。例如,不能編寫如下代碼:
foreach (string friendName in friendNames)
{
friendName = "Rupert the bear";
}
如果編譯這段代碼,就會失敗。但如果使用簡單的for循環,就可以給數組元素賦值。
3.多維數組
多維數組是使用多個索引訪問其元素的數組。例如,假定要確定一座山相對于某位置的高度,可使用兩個坐標x和y來指定一個位置。把這兩個坐標用作索引,讓數組hillHeight可以用每對坐標來存儲高度,這就要使用多維數組了。
像這樣的二維數組可以聲明如下:
<baseType>[, ] <name>;
多維數組只需要更多逗號,例如:
<baseType>[, , , ] <name>;
該語句聲明了一個4維數組。賦值也使用類似的語法,用逗號分隔大小。要聲明和初始化二維數組hillHeight,其基本類型是double, x的大小是3, y的大小是4,則需要:
double[, ] hillHeight = new double[3,4];
還可以使用字面值進行初始賦值。這里使用嵌套的花括號塊,它們之間用逗號分開,例如:
double[, ] hillHeight = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
這個數組的維度與前面的相同,也是3行4列。通過提供字面值隱式定義了這些維度。
要訪問多維數組中的每個元素,只需要指定它們的索引,并用逗號分開,例如:
hillHeight[2,1]
接著就可以像其他元素那樣處理它了。這個表達式將訪問上面定義的第3個嵌套數組中的第2個元素(其值是4)。記住,索引從0開始,第一個數字是嵌套的數組。換言之,第一個數字指定花括號對,第2個數字指定該對花括號中的元素。用圖5-10來表示這個數組。

圖5-10
foreach循環可以訪問多維數組中的所有元素,其方式與訪問一維數組相同,例如:
double[, ] hillHeight = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } }; foreach (double height in hillHeight) { WriteLine("{0}", height); }
元素的輸出順序與賦予字面值的順序相同(這里顯示了元素的標識符而非實際值):
hillHeight[0,0] hillHeight[0,1] hillHeight[0,2] hillHeight[0,3] hillHeight[1,0] hillHeight[1,1] hillHeight[1,2] ...
4.數組的數組
上一節討論的多維數組可稱為矩形數組,這是因為每一行的元素個數都相同。使用上一個示例,任何一個x坐標都可以對應0至3的y坐標。
也可以使用鋸齒數組(jagged array),其中每行的元素個數可能不同。為此,需要有這樣一個數組,其中的每個元素都是另一個數組。也可以有數組的數組的數組,甚至更復雜的數組。但是,注意這些數組都必須有相同的基本類型。
聲明數組的數組時,其語法要求在數組的聲明中指定多個方括號對,例如:
int[][] jaggedIntArray;
但初始化這樣的數組不像初始化多維數組那樣簡單,例如不能采用以下聲明方式:
jaggedIntArray = new int[3][4];
即使這樣做了,也不是很有效,因為使用簡單的多維數組可以較為輕松地取得相同的結果。也不能使用下面的代碼:
jaggedIntArray = { { 1, 2, 3 }, { 1 }, { 1, 2 } };
有兩種方式:可以初始化包含其他數組的數組(為清晰起見,稱其為子數組),然后依次初始化子數組。
jaggedIntArray = new int[2][]; jaggedIntArray[0] = new int[3]; jaggedIntArray[1] = new int[4];
也可以使用上述字面值賦值的一種改進形式:
jaggedIntArray = new int[3][] { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };
也可以進行簡化,把數組的初始化和聲明放在同一行上,如下所示:
int[][] jaggedIntArray = { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };
可以對鋸齒數組使用foreach循環,但通常需要使用嵌套的foreach循環才能得到實際數據。例如,假定下述鋸齒數組包含10個數組,每個數組又包含一個整數數組,其元素是1-10的約數:
int[][] divisors1To10 = { new int[] { 1 }, new int[] { 1, 2 }, new int[] { 1, 3 }, new int[] { 1, 2, 4 }, new int[] { 1, 5 }, new int[] { 1, 2, 3, 6 }, new int[] { 1, 7 }, new int[] { 1, 2, 4, 8 }, new int[] { 1, 3, 9 }, new int[] { 1, 2, 5, 10 } };
下面的代碼會失敗:
foreach (int divisor in divisors1To10) { WriteLine(divisor); }
這是因為數組divisors1To10包含int[ ]元素而不是int元素。正確的做法是循環遍歷每個子數組和數組本身:
foreach (int[] divisorsOfInt in divisors1To10) { foreach(int divisor in divisorsOfInt) { WriteLine(divisor); } }
可以看出,使用鋸齒數組的語法要復雜得多!大多數情況下,使用矩形數組比較簡單,這是一種比較簡單的存儲方式。但是,有時必須使用鋸齒數組,所以知道怎么使用它們是沒有壞處的。一個例子是,使用XML文檔,其中一些元素有子元素,而一些元素沒有。
- Google Flutter Mobile Development Quick Start Guide
- 案例式C語言程序設計
- 動手玩轉Scratch3.0編程:人工智能科創教育指南
- Programming ArcGIS 10.1 with Python Cookbook
- Mastering Unity Shaders and Effects
- Nginx Essentials
- Android傳感器開發與智能設備案例實戰
- OpenCV 3 Blueprints
- Python計算機視覺和自然語言處理
- Mastering Apache Camel
- Android高級開發實戰:UI、NDK與安全
- 高質量程序設計指南:C++/C語言
- Kotlin程序員面試算法寶典
- Mastering R for Quantitative Finance
- MATLAB/Simulink建模與仿真