14.1 数组
14.2 数组的类型数组实际上是由一个变量名称表示的一组同类型的数据元素。每个元素通过变量名称和一个或多个方括号中的索引名称来访问。
定义
- 元素:数组的独立数据项被称作元素。数组的所有元素必须是相同类型的,或继承自相同的类型。
- 秩/维度:数组可以有任何为正数的维度数。数组的维度数称作秩(rank)。
- 维度长度:数组的每一个维度有一个长度,就是这个方向的位置个数。
- 数组长度:数组的所有维度中的元素的总和称为数组的长度。
重要细节
- 数组一旦被创建,大小就固定了。C#不支持动态数组。
- 数组索引号是从0开始的。也就是说,如果维度长度是n,索引号范围是从0到n−1。
C#提供了两种类型的数组:
- 一维数组可以认为是单行元素或元素向量。
- 多维数组是由主向量中的位置组成的,每一个位置本身又是一个数组,称为子数组(sub-array)。子数组向量中的位置本身又是一个子数组。
另外,有两种类型的多维度数组:矩形数组(rectangular array)和交错数组(jagged array),它们有如下特性。
- 矩形数组
- 某个维度的所有子数组有相同长度的多维数组。
- 不管有多少维度,总是使用一组方括号。
- 某个维度的所有子数组有相同长度的多维数组。
- 矩形数组
int x=myArray[4,6,1] //一组方括号
- 交错数组
- 每一个子数组都是独立数组的多维度数组。
- 可以有不同长度的子数组。
- 为数组的每一个维度使用一对方括号。
- 交错数组
jagArray1[2][7][4] //三组方括号
14.3 数组是对象
数组实例是从System.Array继承的对象。由于数组从BCL基类继承,它们也继承了很多的有用的方法,如:
- Rank:返回数组维度数的属性。
- Length:返回数组长度(数组中所有元素的个数)的属性。
数组是引用类型,与所有引用类型一样,有数据的引用以及数据对象本身。引用在栈或堆上,并且数组对象本身总是在堆上。尽管数组总是引用类型,但是数组的元素可以是值类型或引用类型。
- 如果存储的元素都是值类型,数组被称为值类型数组。
- 如果存储在数组中的元素都是引用类型对象,数组被称作引用类型数组。
14.4 一维数组和矩形数组
声明一维数组或矩形数组
14.5 实例化一维数组或矩形数组要声明一维数组或矩形数组,可以在类型和变量名称之间使用一对方括号。
方括号内的逗号就是秩说明符,它们指定了数组的维度数。秩就是逗号数量加1。
基类和秩说明符构成了数组类型。 例:long[] secondArray;
矩形数组声明的示例。注意:
- 可以使用任意多的秩说明符。
- 不能在数组类型区域中放数组维度长度。秩是数组类型的一部分,而维度长度不是类型的一部分。
- 数组声明后,维度数就是固定的了。然而,维度长度直到数组实例化才会被确定。
说明 和C/C++不同,方括号在基类型后,而不在变量名称后。
要实例化数组,我们可以使用数组创建表达式。数组创建表达式由new运算符构成,后面是基类名称和一对方括号。方块号中以逗号分隔每一个维度的长度。
说明: 与对象创建表达式不一样,数组创建表达式不包含圆括号——即使是对于引用类型数组。
14.6 访问数组元素
在数组中使用整型值作为索引来访问数组元素。
- 每一个维度的索引从0开始。
- 方括号内的索引在数组名称之后。
14.7 初始化数组
当数据被创建之后,每一个元素被自动初始化为类型的默认值。对于预定义的类型,整型默认值是0,浮点型的默认值为0.0,布尔型的默认值为false,而引用类型的默认值则是null。
显式初始化一维数组
对于一维数组,我们可以通过在数组实例化的数组创建表达式之后包括初始化列表来设置显式初始值。
- 初始值必须以逗号分隔,并封闭在一组花括号内。
- 维度长度是可选的,因为编译器可以通过初始化值的个数来推断长度。
- 注意,在数组创建表达式和初始化列表中间没有分隔符。也就是说,没有等号或其他连接运算符。
显式初始化矩形数组
要显式初始化矩形数组:
- 每一个初始值向量必须封闭在花括号内。
- 除了初始值,每一个维度的初始化列表和组成部分必须使用逗号分隔。例:int[,] intArray2 = new int[,]{{10,1},{2,10},{11,9}};
初始化矩形数组的语法点
矩形数组使用嵌套的、逗号分隔的初始化列表进行初始化。初始化列表嵌套在花括号内。有时会混淆,因此,对于嵌套、分组和逗号的正确使用,如下技巧可能会有用:
- 逗号用作元素和分组之间的分隔符。
- 逗号不在左花括号之间使用。
- 逗号不在右花括号之前使用。
- 从左向右读佚指示符,指定最后一个数字作为“元素”,其他数字作为“分组”。
快捷语法
隐式类型数组在语句中结合声明、数组创建和初始化时,我们可以省略语法的数组创建表达式部分。
例:int[] arr1 = new int[3]{10,20,30}; 等价于 int[] arr1={10,20,30};
直到现在,我们一直都在数组声明的开始处显式指定数组类型。然而,在C# 3.0中,和其他局部变量一样,数组可以是隐式类型的。也就是说:
- 当初始化数组时,我们可以让编译器根据初始化器的类型来推断数组类型。只要所有初始化器能隐式转换为单个类型,就可以这么做。
- 和隐式类型的局部变量一样,使用var关键字来替代数组类型。
例:int[] intArr1 = new int[]{10,20,30}; 等价于 var intArr2 = new []{10,20,30,40};
14.8 交错数组
交错数组是数组的数组。与矩形数组不同,交错数组的子数组可以有不同数目的元素。
例如,如下代码声明了一个二维交错数组。int[][] jagArr = new int[3][];
- 第一个维度的长度是3。
- 声明可以读作“jagArr是三个int数组的数组”。
声明交错数组
交错数组的声明语法要求每一个维度都有一对独立的方括号。数组变量声明中的方括号数决定了数组的佚。
- 交错数组可以有任何大于1的维度。 例:int[][] SommArr; //秩等于2
- 和矩形数组一样,维度长度不能包括在数组类型的声明部分中。
快捷实例化
数组创建表达式创建的最高级别数组和交错数组的声明相结合。 例如:int[][] jarArr = new int[3][];
不能在声明语句中初始化最高级别数组之外的数组。 例如:int[][] jarArr = new int[3][4]; //是错误的。
实例化交错数组
和其他类型的数组不一样,交错数组的完全初始化不能在一个步骤中完成。由于交错数组是独立数组的数组——每一个数组必须独立创建。实例化完整的交错数组需要如下步骤:
(1) 首先,实例化顶层数组。
(2) 其次,分别实例化每一个子数组,把新建数组的引用赋值给包含它们的数组的合适元素。
例:
int[][] Arr = new int[3][]; //1.实例化顶层数组
Arr[0] = new int[]{10,20,30}; //2.实例化子数组
Arr[1] = new int[]{40,50,60,70}; //3.实例化子数组
Arr[2]= new int[]{80,90,100,110,120};//4.实例化子数组
交错数组中的子数组
由于交错数组中的子数组本身就是数组,因此交错数组中也可能有矩形数组。
数组的继承自System.Array的GetLength(int n)方法来获取数组中指定维度的长度。
14.9 比较矩形数组和交错数组
在CIL中,一维数组有特定的指令用于性能优化。矩形数组没有这些指令,并且不在相同级别进行优化。因此,有时使用一组数组(可以被优化)的交错数组相比矩形数组会更有效率。
另一方面,矩形数组的编程复杂度更小,因为它会被作为一个单元而不是数组的数组。
14.10 foreach语句
foreach语句允许我们连续访问数组中的每一个元素。其实它是一个比较普遍的结构,因为可以和其他集合类型一起使用。
有关foreach的重点如下所示:
- 迭代变量是临时的,只读的,并且和数组中元素的类型相同。foreach语句使用迭代变量来连续表示数组中的每一个元素。
- foreach语句的语法如下,其中:
- Type是数组中元素的类型。我们可以显式提供它的类型,或者,从C#3.0开始,也可以隐式提供它的类型并通过编译器来推断,因为编译器知道数组的类型。
- 标识符是迭代变量的名字。
- 数组名称是要处理的数组名字
- 语句是要为数组中的每一个元素执行一次的单条语句或语句块。
foreach(Type Identifier in ArrayName) //显式类型迭代变量声明。
Statement
foreach(var Identifier in ArrayName) //隐式类型迭代变量声明。
Statement
隐式类型迭代变量声明和显式类型迭代变量声明的语法是等价的。
foreach语句以如下方式工作:
- 从数组的第一个元素开始并把它赋值给迭代变量。
- 它然后执行语句主体。在主体中,我们可以把迭代变量作为数组元素的只读别名。
- 在主体执行之后,foreach语句选择数组中的下一个元素并重复处理。
迭代变量是只读的
由于迭代变量的值是只读的,所以它不能被改变。但是,对于值类型数组和引用类型数组而言效果不一样。
- 对于值类型数组,我们不能改变数组的数据。尝试改变迭代变量中的数据产生了编译时错误消息。
- 对于引用类型,我们仍然不能改变迭代变量,但是迭代变量只是保存了数据的引用,而不是数据本身。因此,我们可以通过迭代变量改变数据。
foreach语句和多维数组
在多维数组中,元素的处理次序是最右边的索引号最先递值。当索引从0到长度减1时,下一个左边的索引被递增,右边的被重置成0.
一个交错数组是数组的数组,我们必须为交错数组中的每一个维度使用独立的foreach语句。foreach语句必须嵌套以每一个嵌套数组都被正确处理。
14.11 数组协变
在某些情况下,即使某个对象不是数组的基类型,我们也可以把它赋值给数组元素。这种属性叫做协变(covariance)。在下面的情况下可以使用协变:
- 数组是引用类型数组。
- 在赋值的对象类型和数组基类型之间有隐式转换或显式转换。
由于在派生类和基类之间总是有隐式转换的,因此总是可以将一个派生类的对象赋值给为基类声明的数组。
说明:值类型数组没有协变。
14.2 数组继承的有用成员
C#数组从System.Array类继承。它们可以从基类继承很多有用的属性和方法。
成员 |
类型 |
生存期 |
意义 |
---|---|---|---|
Rank |
属性 |
实例 |
获取数组的维度 |
Length |
属性 |
实例 |
获取数组中所有维度的元素总和 |
GetLength |
方法 |
实例 |
返回数组的指定维度的长度 |
Clear |
方法 |
静态 |
返回元素的范围为0或null |
Sort |
方法 |
静态 |
在一维数组中对元素进行排序 |
BinarySearch |
方法 |
静态 |
使用二进制搜索,搜索一维数组中的值 |
Clone |
方法 |
实例 |
进行数组的浅复制—复制数组类型数组和引用类型数组的元素 |
Indexof |
方法 |
静态 |
返回一维数组中遇到的第一个值 |
Reverse |
方法 |
静态 |
将一维数组中的某一范围内的元素顺序例过来 |
GetUpperBound |
方法 |
实例 |
获取指定维度的上限 |
Clone方法
Clone方法为数组进行浅复制。也就是说它只创建了数组本身的克隆。如果是引用类型数组,它不会复制元素引用的对象。对于值类型数组和引用类型数组而言,有不同效果。
- 克隆值类型数组会产生两个独立数组。
- 克隆引用类型数组会产生指向相同对象的两个数组。
Clone方法返回object类型的引用,它必须强制转换成数组类型。例:
- int[] intArr1 ={1,2,3};
- int[] intArr2=(int[]) intArr1.Clone();
14.13比较数组类型
总结了三种类型的数组的重要相似点和不同点。