数组

  前面的所有类型有一个共同点:它们都只存储一个值(结构中存储一组值)。有时,需要存储许多数据,这样就会带来不便。有时需要同时存储几个类型相同的值,而不想为每个值使用不同的变量。

  例如,假定要对所有朋友的姓名执行一些操作。可以使用简单的字符串变量,如下所示:

  1. string friendName1 = "Robert Barwell";
  2. string friendName2 = "Mike Parry";
  3. string friendName3 = "Jeremy Beacock";

  但看起来需要做很多工作,特别是需要编写不同的代码来处理每个变量。例如,不能在循环中迭代这个字符串列表。

  另一种方式是使用数组。数组是一个变量的索引列表,存储在数组类型的变量中。例如,有一个数组 friendNames 存储上述 3 个名字。在方括号中指定索引,即可访问该数组中的各个成员,如下所示:

  1. friendNames[<index>]

  这个索引是一个整数,第一个条目的索引是 0,第二个条目的索引是 1,以此类推。这样就可以使用循环遍历所有条目,例如:

  1. int i;
  2. for (i = 0; i < 3; i++)
  3. {
  4. Console.WriteLine("Name with index of {0}: {1}", i, friendNames[i]);
  5. }

  数组有一个基本类型,数组中的各个条目都是这种类型。friendNames 数组的基本类型是字符串,因为它要存储 string 变量。数组的条目通常称为元素。


  1. 声明数组

  以下述方式声明数组:

  1. <baseType>[] <name>;

  其中,<baseType> 可以是任何变量类型,包括本章前面介绍的枚举和结构类型。数组必须在访问之前初始化,不能像下面这样访问数组或给数组元素赋值:

  1. int[] myIntArray;
  2. myIntArray[10] = 5;

  数组的初始化有两种方式。可以字面值形式执行数组的完整内容,也可以指定数组的大小,再使用关键字 new 初始化所有数组元素。

  使用字面值指定数组,只需提供一个用逗号分隔的元素值列表,该列表放在花括号中,例如:

  1. int[] myIntArray = { 5, 9, 10, 2, 99 };

  其中,myIntArray 有 5 个元素,每个元素都被赋予了一个整数值。

  另一种方式需要使用下述语法:

  1. int[] myIntArray = new int[5];

  这里使用关键字 new 显式地初始化数组,用一个常量定义其大小。这种方法会给所有数组元素赋予同一个默认值,对于数值类型来说,其默认值是0。也可以使用非常量来进行初始化,例如:

  1. int[] myIntArray = new int[arraySize];

  还可以组合使用这两种初始化方式:

  1. int[] myIntArray = new int[5] { 5, 9, 10, 2, 99 };

  使用这种方式,数组大小必须与元素个数相匹配。例如,不能编写如下代码:

  1. int[] myIntArray = new int[10] { 5, 9, 10, 2, 99 };

  其中数组定义为有 10 个元素,但只定义了 5 个元素,所以编译会失败。如果使用变量定义其大小,该变量必须是一个常量,例如:

  1. const int arraySize = 5;
  2. int[] myIntArray = new int[arraySize] { 5, 9, 10, 2, 99 };

  如果省略了关键字 const,运行这段代码就会失败。

  与其他变量类型一样,并非必须在声明数组的代码行中初始化该数组。下面的代码是合法的:

  1. int[] myIntArray;
  2. myIntArray = new int[5];
  下面的 “试一试”示例利用了本节引言中的示例,创建并使用一个字符串数组。将下列代码添加到 Program.cs 中:

  1. static void Main(string[] args)
    {
    string[] friendNames = { "Robert Barwell", "Mike Parry", "Jeremy Beacock" };
    Console.WriteLine("Here are {0} of my friends", firendNames.Lenth);
    for ( int i = 0; i < friendsNames.Length; i++ )
    {
    Console.WriteLine(friendNames[i]);
    }
    Console.ReadKey();
    }


  示例的说明  这段代码用 3 个值建立了一个 string 数组,并在 for 循环中把它们列在控制台上。使用 friendNames.Length 来确定数组中的元素个数:

  1. Console.WriteLine("Here are {0} of my friends", friendNames.Length);


    这是获取数组大小的简便方法。在 for 循环中输出值容易出错。例如,把 < 改为 <=,如下所示:

  1. for ( int i = 0; i <= friendNames.Lenth; i++ )
    {
    Console.WriteLine(friendNames[i]);
    }


  这里代码试图访问 friendNames[3]。记住,数组索引从 0 开始,所以最后一个元素是 friendNames[2]。如果试图访问超出数组大小的元素,代码就会出问题。还可以通过一个更具弹性的方法来访问数组的所有成员,即使用 foreach 循环。

  2. foreach 循环

  foreach 循环可以使用一种简便的语法来定位数组中的每个元素:

  1. foreach (<baseType> <name> in <array>)
  2. {
  3. // can use <name> for each element
  4. }

  这个循环会迭代每个元素,依次把每个元素放在变量 <name> 中,且不存在访问非法元素的危险。不需要考虑数组中有多少个元素,并可以确保将在循环中使用每个元素。使用这个循环,可以修改上一个示例中的代码,如下所示:

  1. static void Main(string[] args)
  2. {
  3. string[] friendNames = { "Robert Barwell", "Mike Parry", "Jeremy Beacock" };
  4. Console.WriteLine("Here are {0} of my friends:", friendNames.Length);
  5. foreach ( string firendName in friendNames )
  6. {
  7. Console.WriteLine(friendName);
  8. }
  9. Console.ReadKey();
  10. }

  这段代码的输出结果与前面的 “试一试”示例完全相同。使用这种方法和标准的 for 循环的主要区别在于: foreach 循环对数组内容进行只读访问,所以不能改变任何元素的值。例如,不能编写如下代码:

  1. foreach ( string friendName in friendNames ) { friendName = "Rupert the bear"; }

  如果编译这段代码,就会失败。但如果使用简单的 for 循环,就可以给数组元素赋值。


  3. 多维数组

  多维数组是使用多个索引访问其元素的数组。例如,假定要确定一座山相对于某位置的高度,可使用两个坐标 xy 来指定一个位置。把这两个坐标用作索引,让数组 hillHeight 可以用每对坐标来存储高度,这就要使用多维数组了。

  像这样的二维数组可以声明如下:

  1. <baseType>[,] <name>;

  多维数组只需要更多逗号,例如:

  1. <baseType>[,,,] <name>;

  该语句声明了一个 4 维数组。赋值也使用类似的语法,用逗号分隔大小。要声明和初始化二维数组 hillHeight,其基本类型是 doublex 的大小是 3, y 的大小是 4,则需要:

  1. double[,] hillHeight = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };

  这个数组的维度与前面的相同,也是 3 行 4 列。通过提供字面值隐式定义了这些维度。

  要访问多维数组中的每个元素,只需指定它们的索引,并用逗号分开,例如:

  1. hillHeight[2, 1]

  接着就可以像其他元素那样处理它了。这个表达式将访问上面定义的第 3 个嵌套数组中的第 2 个元素(其值是 4)。记住,索引从 0 开始,第一个数字是嵌套的数组。换言之,第一个数字指定花括号对,第 2 个数字指定该对花括号中的元素。用 图 5-11 来表示这个数组。

 5.2.3 数组  - 图1

  foreach 循环可以访问多维数组中的所有元素,其方式与访问一维数组相同,例如:

  1. double[,] hillHeight = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
  2. foreach ( double height in hillHeight )
  3. {
  4. Console.WriteLine("{0}", height);
  5. }

  元素的输出顺序与赋予字面值的顺序相同(这里显示了元素的标识符,而非实际值):

  1. hillHeight[0, 0]
  2. hillHeight[0, 1]
  3. hillHeight[0, 2]
  4. hillHeight[0, 3]
  5. hillHeight[1, 0]
  6. hillHeight[1, 1]
  7. hillHeight[1, 2]
  8. ...

  4. 数组的数组

  上一节讨论的多维数组可称为矩形数组,这是因为每一行的元素个数都相同。使用上一个示例,任何一个 x 坐标都可以对应 0~3 的 y 坐标。

  也可以使用锯齿数组(jagged array),其中每行的元素个数可能不同。为此,需要有这样一个数组,其中的每个元素都是另一个数组。也可以有数组的数组的数组,甚至更复杂的数组。但是,注意这些数组都必须有相同的基本类型。

  声明数组的数组时,其语法要求在数组的声明中指定多个方括号对,例如:

  1. int[][] jaggedIntArray;

  但初始化这样的数组不像初始化多维数组那样简单,例如不能采用以下的声明方式:

  1. jaggedIntArray = new int[3][4];

  即使这样做了,也不是很有效,因为使用简单的多维数组可以较轻松地取得相同的结果。也不能使用下面的代码:

  1. jaggedIntArray = { {1, 2, 3}, { 1 }, { 1, 2 } };

  有两种方式:可以初始化包含其他数组的数组(为清晰起见,称其为子数组),然后依次初始化子数组:

  1. jaggedIntArray = new int[2][];
  2. jaggedIntArray[0] = new int[3];
  3. jaggedIntArray[1] = new int[4];

  也可以使用上述字面值赋值的一种改进形式:

  1. jaggedIntArray = new int[3][] { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };

  也可以进行简化,把数组的初始化和声明放在同一行上,如下所示:

  1. int[][] jaggedIntArray = { new int[] { 1, 2, 3 }, new int[] { 1 }, new int[] { 1, 2 } };

  可以对锯齿数组使用 foreach 循环,但通常需要使用嵌套的 foreach 循环才能得到实际数据。例如,假定下述锯齿数组包含 10 个数组,每个数组又包含一个整数数组,其元素是 1~10 的约数:

  1. int[][] divisors1To10 = { new int[] { 1 },
  2. new int[] { 1, 2 },
  3. new int[] { 1, 3 },
  4. new int[] { 1, 2, 4 },
  5. new int[] { 1, 5 },
  6. new int[] { 1, 2, 3, 6 },
  7. new int[] { 1, 7 },
  8. new int[] { 1, 2, 4, 8 },
  9. new int[] { 1, 3, 9 },
  10. new int[] { 1, 2, 5, 10 } };

  下面的代码会失败:

  1. foreach ( int divisor in divisors1To10 )
  2. {
  3. Console.WriteLine(divisor);
  4. }

  这是因为数组 divisors1To10 包含 int[] 元素,而不是 int 元素。正确的做法是循环遍历每个子数组和数组本身:

  1. foreach ( int[] divisorsOfInt in divisors1To10 )
  2. {
  3. foreach ( int divisor in divisorsOfInt )
  4. {
  5. Console.WriteLine(divisor);
  6. }
  7. }

  可以看出,使用锯齿数组的语法要复杂很多!大多数情况下,使用矩形数组比较简单,这是一种比较简单的存储方式。但是,有时必须使用锯齿数组,所以知道怎么使用它们是没有坏处的。