1. 数组

数组是一种包含若干变量的数据结构,这些变量都可以通过计算索引进行访问。数组中包含的变量(又称数组的元素)具有相同的类型,该类型称为数组的元素类型。

数组有一个“秩”,它确定和每个数组元素关联的索引个数。数组的秩又称为数组的维度。“秩”为 1 的数组称为一维数组 (single-dimensional array)。“秩”大于 1 的数组称为多维数组 (multi-dimensional array)。维度大小确定的多维数组通常称为两维数组、三维数组等。

数组的每个维度都有一个关联的长度,它是一个大于或等于零的整数。维度的长度不是数组类型的组成部分,而只与数组类型的实例相关联,它是在运行时创建实例时确定的。维度长度确定该维度索引的有效范围:如果维度长度为 N,则索引的范围可以从 0 到 N – 1(包括 N – 1)。数组中的元素总数是数组中各维度长度的乘积。如果数组的一个或多个维度的长度为零,则称该数组为空。

数组的元素类型可以是任意类型,包括数组类型。

1.1 数组类型

数组类型表示为一个 non-array-type 后接一个或多个 rank-specifier:

array-type:
non-array-type   rank-specifiers

non-array-type:
type

rank-specifiers:
rank-specifier
rank-specifiers   rank-specifier

rank-specifier:
[  
dim-separatorsopt   ]

dim-separators:
,
dim-separators   ,

non-array-type 是本身不是 array-type 的任意 type。

由 array-type 中最左侧的 rank-specifier 给定数组类型的秩:rank-specifier 表示该数组是其秩为 1 加上 rank-specifier 中的“,”标记个数的数组。

数组类型的元素类型就是去掉最左边的 rank-specifier 后剩余表达式的类型:

  • 形式为
    T[R] 的数组类型是秩为 R、元素类型为非数组元素类型 T 的数组。
  • 形式为
    T[R][R1]...[RN] 的数组类型是秩为 R、元素类型为 T[R1]...[RN] 的数组。

实质上,在解释数组类型时,先从左到右读取 rank-specifier,最后才读取那个最终的非数组元素类型。例如,类型 int[][,,][,] 表示一个一维数组,该一维数组的元素类型为三维数组,该三维数组的元素类型为二维数组,该二维数组的元素类型为 int。

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

1.1.1 System.Array 类型

System.Array 类型是所有数组类型的抽象基类型。存在从任何数组类型到 System.Array 的隐式引用转换(第 6.1.6 节),并且存在从 System.Array 到任何数组类型的显式引用转换(第 6.2.4) 节)。请注意,System.Array 本身不是 array-type。相反,它是一个从中派生所有 array-type 的 class-type。

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

1.1.2 数组和泛型 IList 接口

一维数组 T[] 实现了接口 System.Collections.Generic.IList<T>(缩写为 IList<T>)及其基接口。相应地,存在从 T[] 到 IList<T> 及其基接口的隐式转换。此外,如果存在从 S 到 T 的隐式引用转换,则 S[] 实现 IList<T>,并且存在从 S[] 到 IList<T> 及其基接口的隐式引用转换(第 6.1.6 节)。如果存在从 S 到 T 的显式引用转换,则存在从 S[] 到 IList<T> 及其基接口的显式引用转换(第 6.2.4 节)。例如:

using
System.Collections.Generic;

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
}
}

赋值操作
lst2 = oa1 将产生编译时错误,因为从
object[] 到 IList<string> 的转换是显式转换,不是隐式转换。强制转换 (IList<string>)oa1 会导致在运行时引发异常,因为 oa1 引用 object[] 而不是 string[]。但是,强制转换 (IList<string>)oa2 不会导致在运行时引发异常,因为 oa2 引用 string[]。

如果存在从 S[] 到 IList<T> 的隐式或显式引用转换,则也存在从 IList<T> 及其基接口到 S[] 的显式引用转换(第 6.2.4 节)。

当数组类型 S[] 实现 IList<T> 时,所实现的接口的有些成员可能会引发异常。该接口的实现的确切行为不在本规范讨论的范围之内。

1.2 数组创建

数组实例是由 array-creation-expression(第 7.6.10.4 节)创建的,或者是由包含 array-initializer(第 12.6 节)的字段声明或局部变量声明创建的。

创建数组实例时,将确定秩和各维度的长度,它们在该实例的整个生存期内保持不变。换言之,对于一个已存在的数组实例,既不能更改它的秩,也不可能调整它的维度大小。

数组实例一定是数组类型。System.Array 类型是不能实例化的抽象类型。

由 array-creation-expression 创建的数组的元素总是被初始化为它们的默认值(第 5.2 节)。

1.3 数组元素访问

数组元素使用形式为 A[I1, I2, ..., IN] 的 element-access 表达式(第 7.6.6.1 节)进行访问,其中 A 是数组类型的表达式,每个 IX 都是 int、uint、long、ulong 类型的表达式,或者可以隐式转换为这些类型的一种或多种类型。数组元素访问的结果是变量,即由下标选定的数组元素。

此外,还可以使用 foreach
语句(第 8.8.4) 节)来枚举数组的各个元素。

1.4 数组成员

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

1.5 数组协变

对于任意两个 reference-type A 和 B,如果存在从 A 到 B 的隐式引用转换(第 6.1.6 节)或显式引用转换(第 6.2.4 节),则也一定存在从数组类型 A[R] 到数组类型 B[R] 的相同的引用转换,其中 R 可以是任何给定的 rank-specifier,但这两个数组类型必须使用相同的 R。这种关系称为数组协变。具体而言,数组协变意味着数组类型 A[R] 的值实际上可能是对数组类型 B[R] 的实例的引用(如果存在从 B 到 A 的隐式引用转换)。

由于存在数组协变,对引用类型数组的元素的赋值操作会包括一个运行时检查,以确保正在赋给数组元素的值确实是允许的类型(第 7.17.1 节)。例如:

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);
}
}

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

具体而言,数组协变不能扩展至 value-type 的数组。例如,不存在允许将 int[] 当作 object[] 来处理的转换。

1.6 数组初始值设定项

数组初始值设定项可以在字段声明(第 10.5 节)、局部变量声明(第 8.5.1 节)和数组创建表达式(第 7.6.10.4 节)中指定:

array-initializer:
{  
variable-initializer-listopt  
}
{  
variable-initializer-list   ,   }

variable-initializer-list:
variable-initializer
variable-initializer-list   ,   variable-initializer

variable-initializer:
expression
array-initializer

数组初始值设定项包含一系列变量初始值设定项,它们括在“{”和“}”标记中并且用“,”标记分隔。每个变量初始值设定项是一个表达式,或者(在多维数组的情况下)是一个嵌套的数组初始值设定项。

数组初始值设定项所在位置的上下文确定了正在被初始化的数组的类型。在数组创建表达式中,数组类型紧靠初始值设定项之前,或者由数组初始值设定项中的表达式推断得出。在字段或变量声明中,数组类型就是所声明的字段或变量的类型。当数组初始值设定项用在字段或变量声明中时,如:

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

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

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

对于一维数组,数组初始值设定项必须包含一个表达式序列,这些表达式是与数组的元素类型兼容的赋值表达式。这些表达式从下标为零的元素开始,按照升序初始化数组元素。数组初始值设定项中所含的表达式的数目确定正在创建的数组实例的长度。例如,上面的数组初始值设定项创建了一个长度为 5 的 int[] 实例并用下列值初始化该实例:

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

对于多维数组,数组初始值设定项必须具有与数组维数同样多的嵌套级别。最外面的嵌套级别对应于最左边的维度,而最里面的嵌套级别对应于最右边的维度。数组各维度的长度是由数组初始值设定项中相应嵌套级别内的元素数目确定的。对于每个嵌套的数组初始值设定项,元素的数目必须与同一级别的其他数组初始值设定项所包含的元素数相同。示例:

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;

如果指定非最右边的维度的长度为零,则假定后续维度的长度也为零。示例:

int[,] c =
{};

创建一个二维数组,其最左边和最右边的维度的长度均为零。

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

当数组创建表达式同时包含显式维度长度和一个数组初始值设定项时,长度必须是常量表达式,并且各嵌套级别的元素数目必须与相应的维度长度匹配。以下是几个示例:

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 的初始值设定项也导致编译时错误。

C# 语言规范_版本5.0 (第12章 数组)的更多相关文章

  1. C# 语言规范_版本5.0 (第2章 词法结构)

    1. 词法结构 1.1 程序 C# 程序 (program) 由一个或多个源文件 (source file) 组成,源文件的正式名称是编译单元 (compilation unit)(第 9.1 节). ...

  2. C# 语言规范_版本5.0 (第4章 类型)

    1. 类型 C# 语言的类型划分为两大类:值类型 (Value type) 和引用类型 (reference type).值类型和引用类型都可以为泛型类型 (generic type),泛型类型采用一 ...

  3. C# 语言规范_版本5.0 (第10章 类)

    1. 类 类是一种数据结构,它可以包含数据成员(常量和字段).函数成员(方法.属性.事件.索引器.运算符.实例构造函数.静态构造函数和析构函数)以及嵌套类型.类类型支持继承,继承是一种机制,它使派生类 ...

  4. C# 语言规范_版本5.0 (第17章 特性)

    1. 特性 C# 语言的一个重要特征是使程序员能够为程序中定义的实体指定声明性信息.例如,类中方法的可访问性是通过使用 method-modifiers(public.protected.intern ...

  5. C# 语言规范_版本5.0 (第11章 结构)

    1. 结构 结构与类的相似之处在于,它们都表示可以包含数据成员和函数成员的数据结构.但是,与类不同,结构是一种值类型,并且不需要堆分配.结构类型的变量直接包含了该结构的数据,而类类型的变量所包含的只是 ...

  6. C# 语言规范_版本5.0 (第8章 语句)

    1. 语句 C# 提供各种语句.使用过 C 和 C++ 编程的开发人员熟悉其中大多数语句. statement: labeled-statement declaration-statement emb ...

  7. C# 语言规范_版本5.0 (第7章 表达式)

    1. 表达式 表达式是一个运算符和操作数的序列.本章定义语法.操作数和运算符的计算顺序以及表达式的含义. 1.1 表达式的分类 一个表达式可归类为下列类别之一: 值.每个值都有关联的类型. 变量.每个 ...

  8. C# 语言规范_版本5.0 (第6章 转换)

    1. 转换 转换(conversion) 使表达式可以被视为一种特定类型.转换可导致将给定类型的表达式视为具有不同的类型,或其可导致没有类型的表达式获得一种类型.转换可以是隐式的 (implicit) ...

  9. C# 语言规范_版本5.0 (第5章 变量)

    1. 变量 变量表示存储位置.每个变量都具有一个类型,用于确定哪些值可以存储在该变量中.C# 是一种类型安全的语言,C# 编译器保证存储在变量中的值总是具有合适的类型.通过赋值或使用 ++ 和 ‑‑ ...

随机推荐

  1. 使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑

    翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑 Part 3: 设计逻辑层:核心开发 如前所述,我们的解决方案 ...

  2. c语言,string库函数strstr实现

    说明: 原型:char *strstr(char *haystack, char *needle); 用法:#include <string.h> 功能:从字符串haystack中寻找ne ...

  3. 关于PDF.NET开发框架对Mysql Sqlite PostgreSQL数据库分页支持的个人看法

    关于PDF.NET开发框架的名字由来  在设计www.pwmis.com站点的时候,考虑到架构的兼容性和将来升级的可能性,最重要的是没有足够的时间去为网站添加和维护很多复杂的程序,所以在借鉴前人成功经 ...

  4. 利用connect建立前端开发服务器

    利用connect建立前端开发服务器 对于前后端完全分离的系统,开发时候我们需要给前端配置服务器,当然我们可以选择Nginx之类的服务器进行配置,但我们也能使用NodeJS构建高自由度的前端开发服务器 ...

  5. cooking eggs

    1: what is egg? what's the shape of it in details? 2: can egg run like this http://item.taobao.com/i ...

  6. 关于《数据格式修改》的简单应用程序(C语言)

    关于<数据格式修改>的简单应用程序(C语言) 至亲爱的博友: 大家好,好久不见了.由于博主还是一名大学在校生,不可避免的需要和指导教师共同完成一些项目,因此前一段时间暂时停止了博客的更新. ...

  7. 如何调用在$(function(){ //内部函数代码 });

    这个文件主要完成如何调用在jquery内部定义的函数,主要有两种方法 法①: <script type="text/javascript"> $(function() ...

  8. EasyMock入门

    这是一个JavaProject,有关EasyMock用法详见本文测试用例 首先是用到的实体类User.java package com.jadyer.model; public class User ...

  9. 深入浅出学习Spring框架(四):IoC和AOP的应用——事务配置

    在前文 深入浅出学习Spring框架(一):通过Demo阐述IoC和DI的优势所在. 深入浅出学习Spring框架(三):AOP 详解 分别介绍了Spring的核心功能——IoC和AOP,光讲知识远远 ...

  10. ASP.NET Zero--5.配置权限

    修改角色的时候,会有一份权限列表,可以给这个角色分配哪些权限,那如何添加一个新权限呢?   这里以添加一个“测试”的权限为例 1.打开AppPermissions.cs [..\MyCompanyNa ...