C# 语言规范_版本5.0 (第12章 数组)

时间:2022-09-23 14:32:59

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&num; 语言规范&lowbar;版本5&period;0 &lpar;第2章 词法结构&rpar;

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

  2. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第4章 类型&rpar;

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

  3. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第10章 类&rpar;

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

  4. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第17章 特性&rpar;

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

  5. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第11章 结构&rpar;

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

  6. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第8章 语句&rpar;

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

  7. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第7章 表达式&rpar;

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

  8. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第6章 转换&rpar;

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

  9. C&num; 语言规范&lowbar;版本5&period;0 &lpar;第5章 变量&rpar;

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

随机推荐

  1. Orchard 模块开发学习笔记 (1)

    创建模块 首先,打开Bin目录下的Orchard.exe 等到出现orchard>后, 看看命令列表中是否存在 codegen module 如果不存在,则需要先执行:feature enabl ...

  2. 创建型模式之Builder模式及实现

    建造者(Builder)模式 GOF给出的定义为:建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 应用场景 使用建造者模式是为了将构建复杂对象的过程和它的部件 ...

  3. OC-&commat;property、self及类的本质

    让代码书写更加简便 --1-- 设置器和访问器 1.1 setter 1.2 getter --2-- 类的本质 2.1 类类型的对象 2.2 类的本质 2.3 如何获取类对象 2.4 类对象的使用 ...

  4. php pdo预处理语句与存储过程

    很多更成熟的数据库都支持预处理语句的概念.什么是预处理语句?可以把它看作是想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制.预处理语句可以带来两大好处: 1.查询仅需解析(或预处理) ...

  5. Ksoap 使用简介

    转:http://www.open-open.com/bbs/view/1320111271749?sort=newest WebService 是一种基于SOAP协议的远程调用标准.通过WebSer ...

  6. 【转】Linux中profile、bashrc、bash&lowbar;profile之间的区别和联系

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登陆时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置. 英文描述为: # /etc/pr ...

  7. webpack与gulp的区别

    gulp是工具链.构建工具,可以配合各种插件做js压缩,css压缩,less编译 替代手工实现自动化工作 1.构建工具 2.自动化 3.提高效率用 webpack是文件打包工具,可以把项目的各种js. ...

  8. 利用 fdisk进行分区

    ):fdisk命令参数 p:打印分区表. n:新建一个新分区. d:删除一个新分区. q:退出不保存. w:退出且保存. 例子: 先看下磁盘: root@archiso ~ # lsblk 在这里对磁 ...

  9. 清除html中的标记,只留下文字

    /// <summary>/// 清除html中的标记,只留下文字./// </summary>/// <param name="HTML">& ...

  10. 爬虫--XPATH解析

    今天说一下关于爬取数据解析的方式---->XPATH,XPATH是解析方式中最重要的一种方式 1.安装:pip install lxml  2.原理 1. 获取页面源码数据 2.实例化一个etr ...