17 个数组

17.1 常规

数组是一种数据结构,其中包含许多通过计算索引访问的变量。 数组中的变量(亦称为数组的元素)均为同一种类型,我们将这种类型称为数组的元素类型

数组具有确定与每个数组元素关联的索引数的排名。 数组的排名也称为数组的维度。 排名为一的数组称为 单维数组。 排名大于一的数组称为 多维数组。 特定大小的多维数组通常称为二维数组、三维数组等。 数组的每个维度都有一个关联的长度,该长度是大于或等于零的整数。 维度长度不是数组类型的一部分,而是在运行时创建数组类型的实例时建立的。 维度的长度决定了该维度的有效索引范围:对于长度N的维度,索引的范围可以是非0N – 1独占的。 数组中的元素总数是数组中每个维度长度的乘积。 如果数组的一个或多个维度的长度为零,则表示该数组为空。

数组的元素类型本身可以是数组类型(§17.2.1)。 数组的此类数组不同于多维数组,可用于表示“交错数组”。

示例:

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

end 示例

每个数组类型都是引用类型(§8.2)。 数组的元素类型可以是任何类型的,包括值类型和数组类型。

17.2 数组类型

17.2.1 常规

数组类型的语法生产在 §8.2.1提供。

数组类型以non_array_type形式写入,后接一个或多个rank_specifier

non_array_type类型本身不是array_type

数组类型的排名由array_type中最左侧的rank_specifier提供:rank_specifier指示数组是一个数组,其排名为一个加上rank_specifier中的,”标记数。

数组类型的元素类型是删除最 左侧rank_specifier的结果类型:

  • 窗体 T[R] 的数组类型是具有排名 R 和非数组元素类型的 T数组。
  • 窗体 T[R][R₁]...[Rₓ] 的数组类型是具有排名 R 和元素类型的 T[R₁]...[Rₓ]数组。

实际上,rank_specifier在最终的非数组元素类型之前从左到右读取。

示例:类型 T[][,,][,] 为二维数组的三维数组的一维数组 intend 示例

在运行时,数组类型的值可以是 null 或对该数组类型的实例的引用。

注意:遵循 §17.6 的规则,该值也可能是对协变数组类型的引用。 end note

17.2.2 System.Array 类型

该类型 System.Array 是所有数组类型的抽象基类型。 隐式引用转换(§10.2.8)从任何数组类型到 System.Array 实现的任何接口类型 System.Array存在。 显式引用转换(§10.3.5)存在于 System.Array 由任何数组类型实现 System.Array 的任何接口类型。 System.Array 本身不是 array_type。 相反,这是一个 class_type ,从中派生所有 array_type

在运行时,类型 System.Array 值可以是 null 或引用任何数组类型的实例。

17.2.3 数组和泛型集合接口

单维数组 T[] 实现接口 System.Collections.Generic.IList<T>IList<T> 短)及其基接口。 因此,从其基接口进行隐式转换T[]IList<T>。 此外,如果有从到然后实现的隐式引用转换ST,并且有从其IList<T>基接口的隐式引用转换S[]§10.2.8)。IList<T> S[] 如果从显式引用转换S到,则从其基接口(§10.3.5)进行显式引用转换S[]IList<T>T

同样,单维数组 T[] 还实现接口 System.Collections.Generic.IReadOnlyList<T>IReadOnlyList<T> 短)及其基接口。 因此,从其基接口进行隐式转换T[]IReadOnlyList<T>。 此外,如果有从到然后实现的隐式引用转换ST,并且有从其IReadOnlyList<T>基接口的隐式引用转换S[]§10.2.8)。IReadOnlyList<T> S[] 如果从显式引用转换S到,则从其基接口(§10.3.5)进行显式引用转换S[]IReadOnlyList<T>T

示例:例如:

class Test
{
    static void Main()
    {
        string[] sa = new string[5];
        object[] oa1 = new object[5];
        object[] oa2 = sa;

        IList<string> lst1 = sa;  // Ok
        IList<string> lst2 = oa1; // Error, cast needed
        IList<object> lst3 = sa;  // Ok
        IList<object> lst4 = oa1; // Ok

        IList<string> lst5 = (IList<string>)oa1; // Exception
        IList<string> lst6 = (IList<string>)oa2; // Ok

        IReadOnlyList<string> lst7 = sa;        // Ok
        IReadOnlyList<string> lst8 = oa1;       // Error, cast needed
        IReadOnlyList<object> lst9 = sa;        // Ok
        IReadOnlyList<object> lst10 = oa1;      // Ok
        IReadOnlyList<string> lst11 = (IReadOnlyList<string>)oa1; // Exception
        IReadOnlyList<string> lst12 = (IReadOnlyList<string>)oa2; // Ok
    }
}

分配 lst2 = oa1 生成编译时错误,因为从转换 object[]IList<string> 的转换是显式转换,而不是隐式转换。 强制转换(IList<string>)oa1将导致在运行时引发异常,因为oa1引用了非object[]string[]引用 。 但是,强制转换(IList<string>)oa2 不会引发异常,因为 oa2 引用了一个 string[]

end 示例

每当有隐式或显式引用转换S[]到时IList<T>,也存在从其基接口到 S[]§10.3.5) 的显式引用转换IList<T>

当数组类型 S[] 实现时,已实现 IList<T>接口的某些成员可能会引发异常。 接口实现的精确行为超出了此规范的范围。

17.3 数组创建

数组实例由array_creation_expression s(§12.8.16.5)或包含array_initializer§17.7)的字段或局部变量声明创建。 还可以隐式创建数组实例,作为计算涉及参数数组的参数列表的一部分(§15.6.2.4)。

创建数组实例时,将建立每个维度的排名和长度,然后在实例的整个生存期内保持常量。 换句话说,无法更改现有数组实例的排名,也不能调整其维度的大小。

数组实例始终是数组类型。 该 System.Array 类型是无法实例化的抽象类型。

array_creation_expression创建的数组元素始终初始化为其默认值(§9.3)。

17.4 Array 元素访问

数组元素是使用表单的element_access表达式(§12.8.11.2)访问的,其中A数组类型的表达式,每个Iₑ表达式是类型intuint类型、longulong可隐式转换为其中一A[I₁, I₂, ..., Iₓ]个或多个类型的表达式。 数组元素访问的结果是一个变量,即索引选择的数组元素。

可以使用语句(§13.9.5)枚举foreach数组的元素。

17.5 数组成员

每个数组类型都继承该 System.Array 类型声明的成员。

17.6 数组协变

对于任意两个reference_typeA并且B,如果隐式引用转换(§10.2.8)或显式引用转换(§10.3.5)存在,B则数组类型A[R]也存在A相同的引用转换,B[R]其中R任何给定rank_specifier(但两种数组类型的引用都相同)。 此关系称为 数组协变。 数组协变(特别是)表示,数组类型的值实际上可能是对数组类型的A[R]B[R]实例的引用,前提是存在B从 到A的隐式引用转换。

由于数组协变,对引用类型数组元素的赋值包括运行时检查,以确保分配给数组元素的值实际上是允许的类型(§12.21.2)。

示例:

class Test
{
    static void Fill(object[] array, int index, int count, object value) 
    {
        for (int i = index; i < index + count; i++)
        {
            array[i] = value;
        }
    }

    static void Main() 
    {
        string[] strings = new string[100];
        Fill(strings, 0, 100, "Undefined");
        Fill(strings, 0, 10, null);
        Fill(strings, 90, 10, 0);
    }
}

方法中Fillarray[i]中的赋值隐式包括运行时检查,该检查可确保value引用null或对与实际元素类型array兼容的类型的对象的引用。 在 Main前两次成功调用 Fill 中,但第三个调用会导致在执行第一个 System.ArrayTypeMismatchException 赋值 array[i]时引发。 发生异常的原因是无法将装箱 int 存储在数组中 string

end 示例

数组协变专门不扩展到 value_type数组。 例如,不存在允许将转换 int[] 视为一个 object[]转换。

17.7 数组初始值设定项

可以在字段声明(§15.5)、局部变量声明(§13.6.2)和数组创建表达式(§12.8.16.5)中指定数组初始值设定项:

array_initializer
    : '{' variable_initializer_list? '}'
    | '{' variable_initializer_list ',' '}'
    ;

variable_initializer_list
    : variable_initializer (',' variable_initializer)*
    ;
    
variable_initializer
    : expression
    | array_initializer
    ;

数组初始值设定项由一系列变量初始值设定项组成,由“”{和“}”标记括起来,并用“,”标记分隔。 每个变量初始值设定项都是表达式,或者,对于多维数组,是嵌套数组初始值设定项。

在其中使用数组初始值设定项的上下文决定了要初始化的数组的类型。 在数组创建表达式中,数组类型紧邻初始值设定项之前,或从数组初始值设定项中的表达式推断。 在字段或变量声明中,数组类型是声明的字段或变量的类型。 在字段或变量声明中使用数组初始值设定项时,

int[] a = {0, 2, 4, 6, 8};

它只是等效数组创建表达式的简写:

int[] a = new int[] {0, 2, 4, 6, 8};

对于单维数组,数组初始值设定项应包含一系列表达式,每个表达式都具有对数组的元素类型的隐式转换(§10.2)。 表达式以递增顺序初始化数组元素,从索引为零的元素开始。 数组初始值设定项中的表达式数决定了要创建的数组实例的长度。

示例:上面的数组初始值设定项创建 int[] 长度为 5 的实例,然后使用以下值初始化实例:

a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;

end 示例

对于多维数组,数组初始值设定项的嵌套级别应与数组中存在维度一样多。 最外部的嵌套级别对应于最左侧的维度,最内部的嵌套级别对应于最右侧的维度。 数组的每个维度的长度取决于数组初始值设定项中相应嵌套级别的元素数。 对于每个嵌套数组初始值设定项,元素数应与同一级别的其他数组初始值设定项相同。

示例:示例:

int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};

为最左侧的维度创建长度为 5 的二维数组,最右侧维度的长度为 2:

int[,] b = new int[5, 2];

然后使用以下值初始化数组实例:

b[0, 0] = 0; b[0, 1] = 1;
b[1, 0] = 2; b[1, 1] = 3;
b[2, 0] = 4; b[2, 1] = 5;
b[3, 0] = 6; b[3, 1] = 7;
b[4, 0] = 8; b[4, 1] = 9;

end 示例

如果除最右侧以外的维度具有长度为零,则假定后续维度的长度也为零。

示例:

int[,] c = {};

为最左侧和最右侧的维度创建长度为零的二维数组:

int[,] c = new int[0, 0];

end 示例

当数组创建表达式包括显式维度长度和数组初始值设定项时,长度应为常量表达式,每个嵌套级别的元素数应与相应的维度长度匹配。

示例:下面是一些示例:

int i = 3;
int[] x = new int[3] {0, 1, 2}; // OK
int[] y = new int[i] {0, 1, 2}; // Error, i not a constant
int[] z = new int[3] {0, 1, 2, 3}; // Error, length/initializer mismatch

此处,由于维度长度表达式不是常量而生成编译时错误的初始值设定 y 项,因此会导致编译时错误,因为初始值设定 z 项中的长度和元素数不同意。

end 示例

注意:C# 允许在array_initializer末尾使用尾随逗号。 此语法提供了从此类列表中添加或删除成员的灵活性,并简化了此类列表的计算机生成。 end note