C# 语言规范_版本5.0 (第12章 数组)
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章 数组)的更多相关文章
- C# 语言规范_版本5.0 (第2章 词法结构)
1. 词法结构 1.1 程序 C# 程序 (program) 由一个或多个源文件 (source file) 组成,源文件的正式名称是编译单元 (compilation unit)(第 9.1 节). ...
- C# 语言规范_版本5.0 (第4章 类型)
1. 类型 C# 语言的类型划分为两大类:值类型 (Value type) 和引用类型 (reference type).值类型和引用类型都可以为泛型类型 (generic type),泛型类型采用一 ...
- C# 语言规范_版本5.0 (第10章 类)
1. 类 类是一种数据结构,它可以包含数据成员(常量和字段).函数成员(方法.属性.事件.索引器.运算符.实例构造函数.静态构造函数和析构函数)以及嵌套类型.类类型支持继承,继承是一种机制,它使派生类 ...
- C# 语言规范_版本5.0 (第17章 特性)
1. 特性 C# 语言的一个重要特征是使程序员能够为程序中定义的实体指定声明性信息.例如,类中方法的可访问性是通过使用 method-modifiers(public.protected.intern ...
- C# 语言规范_版本5.0 (第11章 结构)
1. 结构 结构与类的相似之处在于,它们都表示可以包含数据成员和函数成员的数据结构.但是,与类不同,结构是一种值类型,并且不需要堆分配.结构类型的变量直接包含了该结构的数据,而类类型的变量所包含的只是 ...
- C# 语言规范_版本5.0 (第8章 语句)
1. 语句 C# 提供各种语句.使用过 C 和 C++ 编程的开发人员熟悉其中大多数语句. statement: labeled-statement declaration-statement emb ...
- C# 语言规范_版本5.0 (第7章 表达式)
1. 表达式 表达式是一个运算符和操作数的序列.本章定义语法.操作数和运算符的计算顺序以及表达式的含义. 1.1 表达式的分类 一个表达式可归类为下列类别之一: 值.每个值都有关联的类型. 变量.每个 ...
- C# 语言规范_版本5.0 (第6章 转换)
1. 转换 转换(conversion) 使表达式可以被视为一种特定类型.转换可导致将给定类型的表达式视为具有不同的类型,或其可导致没有类型的表达式获得一种类型.转换可以是隐式的 (implicit) ...
- C# 语言规范_版本5.0 (第5章 变量)
1. 变量 变量表示存储位置.每个变量都具有一个类型,用于确定哪些值可以存储在该变量中.C# 是一种类型安全的语言,C# 编译器保证存储在变量中的值总是具有合适的类型.通过赋值或使用 ++ 和 ‑‑ ...
随机推荐
- 新浪微博SDK在Eclipse引入
新浪微博SDK在Eclipse中的使用 新浪微博SDK在Eclipse中的使用 今天在看<Android开发应用实战>,全书都在讲一个android版的新浪微博客户端怎么做,于是按照书上步 ...
- JavaScript原生对象拓展
JavaScript原生对象拓展 在据说每个大牛.小牛都应该有自己的库——框架篇中我扬言要做个小牛,没想到一天没更新,小伙儿伴们就戏谑的问我,油哥是不是要太监了?其实事情是这个样子的,这不是太监的节奏 ...
- JavaScript判断对象的类型
JavaScript判断对象的类型 最近阅读了一些关于JavaScript判断对象类型的文章.总结下来,主要有constructor属性.typeof操作符.instanceof操作符和Object. ...
- webservice 第一节 .net SoapHeader验证
在工作中经常用到webservice,在.net 开发中经常用到webservice,在java开发经常用到cxf. 今天闲置没事就介绍下 .net webservice中常用到 soapheader ...
- MVVM与Knockout
MVVM与Knockout 前言 今天搞的有点快,因为上午简单研究了下MVC,发现MVC不太适合前端开发,然后之前看几位前端前辈都推荐前端使用MVVM,但是我对其还不甚了解,所以我觉得下午还是应该先看 ...
- Archives for the category: Fisheye/Crucible
Archives for the category: Fisheye/Crucible Introducing FishEye and Crucible 3.0 – Search, visualize ...
- [基础]RHEL6下LINUX服务器批量部署
包准备:xinetd,tftp-server,dhcp,httpd,system-config-kickstart,syslinux,nfs 试验环境: 本机地址:192.168.46.98 ...
- CloudFoundry虚拟机实例配置DNS
使用Bosh成功部署CloudFoundry后,在OpenStack上启了一个实例作为DNS服务器专用,配置域名mycloud.com解析到CF API接口的IP:10.68.19.134,然后使用C ...
- 黑马程序员:Java基础总结----正则表达式
黑马程序员:Java基础总结 正则表达式 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 正则表达式 import java.util.regex.*; 符合一定规 ...
- 简单讨论一下 jQuery 事件
事件是 Web 应用中不可或缺的一个东西,用户在应用中执行一个操作的时候,比如鼠标单击时要触发执行一些事情,就可以给该事件绑定一个事件处理程序(event handler).使用 jQuery 的 . ...