D语言中的数组(一)

数组

有四种数组:
int* p; 指向数据的指针
int[3] s; 静态数组
int[] a; 动态数组
int[char[]] x; 关联数组

指针

	int* p;
	
这是最简单的指向数据的指针,等价于 C 指针。提供指针的目的是提供与 C 的接口并给 D 完成特定的系统级工作的能力。没有与之相关的数组长度,所以就没有办法在编译或运行时进行越界检查之类的工作。大多数传统的指针用法可以使用动态数组、 outinout 参数及引用型别代替。

静态数组

	int[3] s;
	
这就是C语言的数组。静态数组的长度是在编译时就确定的。

动态数组

	int[] a;
	
动态数组由数组长度和指向数据的指针组成。多个动态数组可能共享数组中的全部或者部分数据。

数组声明

有两种声明数组的方式:前缀法和后缀法。前缀法通常更好一些,尤其是用于声明非平凡型别的数组时。
前缀数组声明
前缀声明出现在被声明的标志符之前,应该从右向左读,所以:
	int[] a;	         // int 的动态数组
	int[4][3] b;	// 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
	int[][5] c;	// 含有 5 个元素的数组,每个元素是 int 的动态数组
	int*[]*[3] d;	// 含有 3 个元素的数组,每个元素是指向含指向 int 的动态数组的指针
	int[]* e;		// 所指元素为动态数组的指针
	
后缀数组声明
后缀声明出现在被声明的标志符之后,并且从左向右读。下面的每一组内的声明都是等价的:
	// int 的动态数组
	int[] a;
	int a[];

	// 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
	int[4][3] b;
	int[4] b[3];
	int b[3][4];

	// 含有 5 个元素的数组,每个元素是 int 的动态数组
	int[][5] c;
	int[] c[5];
	int c[5][];

	// 含有 3 个元素的数组,每个元素是指向含指向 int 的动态数组的指针
	int*[]*[3] d;
	int*[]* d[3];
	int* (*d[3])[];

	// 所指元素为动态数组的指针
	int[]* e;
	int (*e[]);
	
原理:后缀形式同 C 和 C++ 采用的形式完全一样,提供这种支持会给用惯了这种形式的程序员提供一条容易的迁移路径。

用法

对数组的操作可以分为两大类——对数组引用的操作和对数组内容的操作。在 D 中,这两种操作都有。

数组名其实就是数组的引用,如 p、s 或者 a :

	int* p;
	int[3] s;
	int[] a;

	int* q;
	int[3] t;
	int[] b;

	p = q;		// p 和 q 指向同一个东西
	p = s;		// p 指向数组 s 的第一个元素
	p = a;		// p 指向数组 a 的第一个元素

	s = ...;		// 错误,因为 s 是一个编译时静态数组引用

	a = p;		// 错误,因为 p 不带有数组的长度
	a = s;		// 被初始化为指向数组 s
	a = b;		// a 和 b 指向同一个数组    

切片

切割 一个数组意味着得到它的一个子数组。数组切片并不复制数组的数据,它只是返回另一个引用。例如:
	int[10] a;	// 声明一个含有 10 个 int 数组
	int[] b;

	b = a[1..3];	// a[1..3] 是含有 2 个元素的数组,它的内容是 a[1] 和 a[2]
	foo(b[1]);	// 等价于 foo(0)
	a[2] = 3;
	foo(b[1]);	// 等价于 foo(3)
    
简写方式 [] 代表整个数组。例如,下面这些对 b 的赋值:
	int[10] a;
	int[] b;

	b = a;
	b = a[];
	b = a[0 .. a.length];
    
在语义上都是等价的。

切割不只是在引用数组中的某个部分时很方便,还可以将指针转换为带有越界检查的数组:

	int* p;
	int[] b = p[0..8];
    
只有当切片的下界按照字节对齐时才允许进行位数组切割:
	bit[] b;
	...
	b[0..8];		// ok
	b[8..16];		// ok
	b[8..17];		// ok
	b[1..16];		// 错误,下界没有按字节对齐
    
未对齐的数组切片将在运行时抛出一个 ArrayBoundsError 异常。

数组复制

当切割运算符作为赋值表达式左值出现时,意味着赋值的目标是数组的内容而不是对数组的引用。当左值是一个切片,右值是一个数组或者对应类型的指针时,会发生复制。
	int[3] s;
	int[3] t;

	s[] = t;			// t[3] 的 3 个元素被复制到 s[3] 中
	s[] = t[];		// t[3] 的 3 个元素被复制到 s[3] 中
	s[1..2] = t[0..1];		// 等价于 s[1] = t[0]
	s[0..2] = t[1..3];		// 等价于 s[0] = t[1], s[1] = t[2]
	s[0..4] = t[0..4];		// 错误,s 中只有 3 个元素
	s[0..2] = t;		// 错误,左值和右值的长度不同    
重叠的复制会被视为错误:
	s[0..2] = s[1..3];		// 错误,重叠的复制
	s[1..3] = s[0..2];		// 错误,重叠的复制
相对于 C 的串行语义来说,禁止重叠的复制使编译器可以进行更为激进的并行代码优化。

数组赋值

如果一个切割运算符作为赋值表达式的左值出现,并且右值的类型同数组元素的类型相同,那么作为左值的数组的内容将被设置为右值的值。
	int[3] s;
	int* p;

	s[] = 3;		// 等价于 s[0] = 3, s[1] = 3, s[2] = 3
	p[0..2] = 3;	// 等价于 p[0] = 3, p[1] = 3
    

数组连接

二元运算符 ~ 是连接运算符。它用来连接数组:
	int[] a;
	int[] b;
	int[] c;

	a = b ~ c;	// 将两个数组连接构成一个新的数组
    
许多语言重载了 + 运算符完成连接操作。这会产生令人不解的结果:
	"10" + 3
    
它的值等于 13 还是字符串 "103" ?这并不是一目了然的,语言设计者不得不抖擞精神,小心地编写规则来避免模棱两可的情况——而这些规则难以正确实现、容易被忽视、忘记。如果使用 + 代表加法,使用另一个运算符代表数组连接会好得多。

类似地,~= 运算符代表追加, 如:

	a ~= b;		// a 变为 a 和 b 的连接的结果
    
连接操作总是创建操作数个一份拷贝,即使一个操作数时长度为 0 的数组时也是如此:
	a = b			// a 指向 b
	a = b ~ c[0..0]		// a 指向 b 的一个拷贝
    

数组运算

注意: 数组运算尚未实现。

总的来说,(a[n..m] op e) 被定义为:

	for (i = n; i < m; i++)
	    a[i] op e;
    
所以,对于表达式:
	a[] = b[] + 3;
    
结果等价于:
	for (i = 0; i < a.length; i++)
	    a[i] = b[i] + 3; 
    
当表达式中出现多于一个的 [] 运算符时,其代表的范围必须匹配。
	a[1..3] = b[] + 3;		// 错误,2 个元素的数组不能同 3 个元素的数组运算
    
示例:
	int[3] abc;		// 包含 3 个 int 的静态数组
	int[] def = [ 1, 2, 3 ];	// 包含 3 个 int 的动态数组

	void dibb(int *array)
	{
		array[2];		// 等价于 *(array + 2)
		*(array + 2);	// 得到下标为 2 的元素
	}

	void diss(int[] array)
	{
		array[2];		// ok
		*(array + 2);	// 错误,数组不是指针
	}

	void ditt(int[3] array)
	{
		array[2];		// ok
		*(array + 2);	// 错误,数组不是指针
	}
	

矩形数组

有经验的 FORTRAN 数值计算程序员都知道多维“矩形”数组对于类似矩阵运算的东西来说大大快于通过指向指针的指针的访问方式(就像“存储数组指针的数组”那样的语义)。例如,D 的语法:
	double[][] matrix;
	
声明了 matrix 为元素为指向数组指针的数组(动态数组被实现为指向数组数据的指针)。因为数组可以有不同的大小(可以动态设置大小),所以有时它被称作“交替数组”。对于代码优化来说更为糟糕的是,数组的行有时会互相指向!幸运的是,D 的静态数组,使用相同的语法,被实现为固定的矩形分布:
	double[3][3] matrix;
	
它声明了一个 3 行 3 列的矩形矩阵,在内存中连续分布。在其他语言中,它被称为多维数组并且声明类似于:
	double matrix[3,3];
	

数组长度

在静态或动态数组的 [ ] 中,变量 length 被隐式的声明并被设为数组的长度。
	int[4] foo;
	int[]  bar = foo;
	int*   p = &foo[0];

	// 下面这些表达式是等价的:
	bar[]
	bar[0 .. 4]
	bar[0 .. length]
	bar[0 .. bar.length]

	p[0 .. length]	// 'length' 未定义,因为 p 不是数组
	bar[0]+length	// 'length' 未定义,因为它不在 [ ] 内

	bar[length-1]	// 得到数组内的最后一个元素
	

数组属性

静态数组的属性有:
sizeof 返回以字节为单位的数组的大小。
length 返回数组中元素的个数。对于静态数组来说这是一个定值。
dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
reverse 将数组中的元素按原来的逆序排列。返回数组。
sort 将数组中的元素按照适当的方法排序。返回数组。

动态数组的属性有:

sizeof 返回动态数组引用的大小,在 32 位平台上是 8 。
length Get/set 数组中元素的个数。
dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
reverse 将数组中的元素按原来的逆序排列。返回数组。
sort 将数组中的元素按照适当的方法排序。返回数组。

示例:

	p.length		// 错误,对于指针来说没有数组大小的信息
	s.length		// 编译时常量 3
	a.length		// 运行时值

	p.dup		// 错误,长度未知,无法复制
	s.dup		// 创建一个含有 3 个元素的数组,将元素复制到它内
	a.dup		// 创建一个含有 a.length 个元素的数组,将元素复制到它内
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值