设置动态数组的长度
动态数组的 .length 属性可以作为 = 运算符的左值:array.length = 7;这会造成数组被在适当的位置被重新分配,现有的内容被原封不动的复制到新的数组中。如果新的数组比原数组短,将只保留新数组能够容纳的那些内容。如果新数组比原数组长,新数组长出的部分将会使用元素的默认初始值填充。
为了达到最高的效率,运行时总是试图适当地调整数组的大小(resize)以避免额外的复制工作。如果数组不是由 new 运算符或者上一个 resize 操作分配的,并且新数组比原数组大,那么运行时将总是进行复制。
这意味着如果有切片依赖于要被重设长度的数组的话,新的数组可能会同那个切片重叠,也就是:
char[] a = new char[20]; char[] b = a[0..10]; char[] c = a[10..20]; b.length = 15; // 总是会被适当的重置大小,因为它是位于 a[] 上的一个切片, // 而 a[] 的长度大于 15 b[11] = 'x'; // a[15] 和 c[5] 也受到影响 a.length = 1; a.length = 20; // 内存分布没有改变
c.length = 12; // 总是会进行复制,因为 c[] 不在 gc 分配的块的开始处
c[5] = 'y'; // 不影响 a[] 和 b[] 的内容 a.length = 25; // 可能会也可能不会进行复制 a[3] = 'z'; // 可能会也可能不会影响到 b[3] ,b[3] 仍然同原来的 a[3] 重叠如果要确保总是进行复制,使用 .dup 属性,这样会得到一个可以进行 resize 的独立数组。
这些讨论也同样适用于使用 ~ 和 ~= 运算符进行的连接操作。
重置动态数组的大小是一个相对昂贵的操作。所以,尽管下面这个用于填充数组的方法结果正确:
int[] array; while (1) { c = getinput(); if (!c) break; array.length = array.length + 1; array[array.length - 1] = c; }它的效率也不会高。一个更为实际的方法是尽量减少对 resize 操作的使用:
int[] array; array.length = 100; // 猜测 for (i = 0; 1; i++) { c = getinput(); if (!c) break; if (i == array.length) array.length = array.length * 2; array[i] = c; } array.length = i;选择一个好的猜测值是一门艺术,但是通常你能找到一个适用于 99% 的情况的值。例如,当从控制台采集用户输入时——长度不太可能超过 80 。
数组越界检查
如果使用小于 0 或大于等于数组长度的数作为数组下标是一个错误。若下标越界,如果是由运行时检测到,会抛出一个 ArrayBoundsError 异常;如果是在编译时检测到,会产生一个错误。程序不应该依赖于数组越界异常检查,例如,下面这个程序的方法是错误的:try { for (i = 0; ; i++) { array[i] = 5; } } catch (ArrayBoundsError) { // 终止循环 }正确的写法是:
for (i = 0; i < array.length; i++) { array[i] = 5; }实现注记:编译器应该在编译时尝试检查数组越界错误,例如:
int[3] foo; int x = foo[3]; // 错误,越界应该可以在编译时通过一个选项决定是否插入动态数组越界检查代码。
数组初始化
- 指针被初始化为 null 。
- 静态数组的内容被初始化为数组元素类型的默认初始值。
- 动态数组被初始化为拥有 0 个元素。
- 关联数组被初始化为拥有 0 个元素。
静态数组的静态初始化
int[3] a = [ 1:2, 3 ]; // a[0] = 0, a[1] = 2, a[2] = 3当数组下标识为枚举类型时,这种写法是最方便的:
enum Color { red, blue, green }; int value[Color.max] = [ blue:6, green:2, red:5 ];如果有数组成员被初始化了,那么所有成员就必须都被初始化。这用于捕捉一种常见的错误:一个新元素被加入到枚举类型中,但是使用枚举的数组的静态实例的初始化列表却忘了更新。
特殊数组型别
位数组
可以构造位数组:bit[10] x; // 10 位的数组实际使用的存储量是依赖于实现的。 实现注记:在 Intel CPU 上,应该按照 32 位对齐。
x.length // 10,位数 x.size // 4,存储所用的字节数如上所示,每个元素的大小不是 (x.size / x.length) 。
字符串
语言应该善于处理字符串。C 和 C++ 对此并不擅长。主要的困难是内存分配、处理临时对象、总是需要扫描字符串以查找终结符 0 及固定的数组长度。D 中的动态数组提供了一个显然的解决方案——字符串就是一个以字符为元素的动态数组。字符串文字量就是一种写字符数组的简单的方法。
char[] str; char[] str1 = "abc";char[] 字符串采用 UTF-8 格式。wchar[] 字符串采用 UTF-16 格式。dchar[] 字符串采用 UTF-32 格式。
字符串可以使用显而易见的语义复制、比较、连接和追加:
str1 = str2; if (str1 < str3) ... func(str3 ~ str4); str4 ~= str1;所有生成的临时对象都会被垃圾收集程序回收(或者使用 alloca())。而且,这对于所有的数组都适用,而不仅仅是对字符串数组。
可以使用指向 char 的指针:
char *p = &str[3]; // 指向第四个元素 char *p = str; // 指向第一个元素但是,因为 D 中的字符串数组不是以 0 终止的,所以如果要把一个字符串指针传递给 C 时,应该加上终止的 0 :
str ~= "/0";字符串的类型在编译的语义分析阶段决定。类型是下列之一:char[]、wchar[]、dchar[] ,并且按照隐式转换规则决定。如果有两个可用的等价的转换,会被认为是一个错误。为了避免出现这种模棱两可的局面,应该使用类型转换:
cast(wchar [])"abc" // 这是 wchar 字符数组如果需要的话,字符串文字量会隐式地在 char、wchar 和 dchar 之间转换。
长度为一个字符的字符串可以被转换为 char、wchar 或者 dchar 常量:
char c; wchar w; dchar d; c = 'b'; // c 被赋值为 char 字符 'b' w = 'b'; // w 被赋值为 wchar 字符 'b' w = 'bc'; // 错误——只能使用一个 wchar 字符 w = "b"[0]; // w 被赋值为 wchar 字符 'b' w = '/r'; // w 被赋值为 wchar 字符“回车” d = 'd'; // d 被赋值为 dchar 字符 'd'
printf() 和字符串
printf() 是一个 C 函数,它不是 D 的一部分。 printf() 输出以 0 终止的 C 字符串。有两种用 printf() 输出 D 字符串的方法。第一种方法是加一个终止的 0 ,并将结果转换为 char* :str ~= "/0"; printf("the string is '%s'/n", (char *)str);第二种方法使用精度指示符。D 数组的内存分布是:数组长度、字符串,所以下面这种方法可以工作:
printf("the string is '%.*s'/n", str);将来,或许应该为 printf() 添加一个新的格式指示符代替这种依赖于实现细节的方法。
隐式转换
指针 T* 可以被隐式的转换为下面的东西:- U* ,其中 U 是 T 的基类。
- void*
- T*
- T[]
- U[dim] ,其中 U 是 T 的基类。
- U[] ,其中 U 是 T 的基类。
- U* ,其中 U 是 T 的基类。
- void*
- void[]
- T*
- U[] ,其中 U 是 T 的基类。
- U* ,其中 U 是 T 的基类。
- void*
关联数组
D 在数组上走得更远——加入了关联数组。关联数组的下标不一定非得是一个数组,因而有广泛的用途。关联数组的下标叫做 关键字 。在关联数组的声明中,关键字 的类型声明位于 [] 内:
int[char[]] b; // 关联数组 b 以 int 为数组元素,下标为字符数组 b["hello"] = 3; // 设定关键字 "hello" 对应的值为 3 func(b["hello"]); // 传递给 func() 的参数为 3可以使用 delete 运算符删除关联数组中的某个键值:
delete b["hello"];这很容易让人误认为删除了 b["hello"] 的值,但是其实并没有这样,这个操作只是从关联数组中删除了关键字 "hello" 。
In表达式 返回一个代表关键字是否在关联数组中的布尔值:
if ("hello" in b) ...关键字的类型不能是函数或者 void 。
属性
关联数组的属性有:sizeof | 返回指向关联数组的引用的大小;典型值是 8 。 |
length | 返回关联数组中值的个数。与动态数组不同的是,它是只读的。 |
keys | 返回动态数组,数组的元素是关联数组的关键字。 |
values | 返回动态数组,数组的元素是关联数组的值。 |
rehash | 适当地重新组织关联数组以提高查找效率。例如,程序已经载入了符号表并将开始进行查找事,rehash 会提高效率。返回指向新数组的引用。 |
关联数组示例:单词计数
import std.file; // D 文件 I/O int main (char[][] args) { int word_total; int line_total; int char_total; int[char[]] dictionary; printf(" lines words bytes file/n"); for (int i = 1; i < args.length; ++i) // 程序参数 { char[] input; // 输入缓冲 int w_cnt, l_cnt, c_cnt; // word、line、char 计数 int inword; int wstart; input = std.file.read(args[i]); // 将文件读入到 input[] foreach (char c; input) { if (c == '/n') ++l_cnt; if (c >= '0' && c <= '9') { } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') { if (!inword) { wstart = j; inword = 1; ++w_cnt; } } else if (inword) { char[] word = input[wstart .. j]; dictionary[word]++; // 递增 word 的计数 inword = 0; } ++c_cnt; } if (inword) { char[] word = input[wstart .. input.length]; dictionary[word]++; } printf("%8ld%8ld%8ld %.*s/n", l_cnt, w_cnt, c_cnt, args[i]); line_total += l_cnt; word_total += w_cnt; char_total += c_cnt; } if (args.length > 2) { printf("-------------------------------------/n%8ld%8ld%8ld total", line_total, word_total, char_total); } printf("-------------------------------------/n"); char[][] keys = dictionary.keys; // 查找 dictionary[] 中的所有 word for (int i = 0; i < keys.length; i++) { char[] word; word = keys[i]; printf("%3d %.*s/n", dictionary[word], word); } return 0; }