官术网_书友最值得收藏!

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文檔,其中一些元素有子元素,而一些元素沒有。

主站蜘蛛池模板: 监利县| 武冈市| 渝中区| 喀喇| 丹巴县| 崇明县| 洪雅县| 阳高县| 宜春市| 乌什县| 建德市| 仪征市| 佛山市| 昭觉县| 巴马| 乌拉特中旗| 兴宁市| 子长县| 和顺县| 棋牌| 和林格尔县| 保康县| 绥宁县| 龙井市| 衡水市| 修文县| 杭锦旗| 平昌县| 玉山县| 建昌县| 庄河市| 榆林市| 东丰县| 宝兴县| 涡阳县| 乌拉特中旗| 运城市| 洛宁县| 孟村| 泾源县| 兴山县|