- 浏览: 220992 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
dysking:
SWT 和 JFace -
wangyuhfut:
东西不少啊。学习中。。。
一个比较好、中文说明的emacs配置文件 1 -
pacocai:
呵呵!学习,学习~~~不过要说编辑器的话个人更喜欢用VIM,比 ...
一个比较好、中文说明的emacs配置文件 1 -
zhf1zhf2:
这两百多个记起来也不容易啊
英国人是这样背的! -
regex:
试了两次,都是乱码,版本是23.1.1,看来不适合
汉化 Emacs 菜单
[size=small]
1 数组类别
有四种数组(arrays):
int* p; 指向数据的指针
int[3] s; 静态数组
int[ ] a; 动态数组
int[char[ ] ] x; 关联数组
1.1 指针(Pointers)
int* p;
它们是简单的指向数据的指针,等价于 C 语言的指针。
这些指针的用于提供与 C 的接口,以及用于一些特定的系统工作。由于它没有相关联的长度特性,所以对于在编译或运行时进行越界检查这类工作就没有办法。
大多数传统的指针用法可以通过使用动态数组、out 和ref 参数以及引用类型来代替。
1.2 静态数组(Static Arrays)
int[3] s;
这个类似于 C 语言的数组。静态数组与众不同的地方就是其 长度 是在编译时 固定 的。
静态数组总的大小不能超过 16Mb 。对于这样大的数组可以使用动态数组来代替。
维数为 0 的静态数组是允许的,只是不会为它分配空间。它可以用作做动态长度的 结构 的
最后一个成员,或者用作 模板扩展 的退化情况(degenerate case)。
1.3 动态数组(Dynamic Arrays)
int[ ] a;
动态数组 由数组 长度 和指向数据的 指针 组成。多个动态数组 可能 共享 数组中的全部或者部分数据。
2 数组声明(Array Declarations)
有两种声明数组的方式:前缀式 和 后缀式。前缀形式是 首选 的方法,尤其对于一些非普通
(non-trivial)类型。
2.1.1 前缀 数组声明
前缀声明出现在被声明的标志符之前,并从右至左读,于是有:
int[ ] a; // int 型动态数组
int[4][3] b; // 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
int[ ][5] c; // 含有 5 个元素的数组,每个元素是 int 的动态数组
int*[ ]*[3] d; // 含有 3 个元素的数组,每个元素是指向含指向 int 的 动态数组 的 指针
int[ ]* e; // 所指元素为动态数组的指针
2.1.2 后缀数组声明
后缀声明出现在被声明的标志符之后,并且从左至右读。下面的每一组内的声明都是等价
的:
// 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++ 采用的形式完全一样,提供这种支持会给用惯了这种形
式的程序员提供一条容易的移植的途径。
3 用法(Usage)
对数组的操作可以分为两类——对数组 引用 的操作和对数组 内容 的操作。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 指向同一个数组
10.4 分割(Slicing)
分割(Slicing) 一个数组表示的是指定它的一个 子数组。一个数组分割并不会复制数据,它仅仅是该数组的 又一个引用。
例如:
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];
5 数组复制(Array Copying)
当分割运算符 作为赋值表达式 左值 出现时,意味着赋值的 目标 是数组的 内容 而不是对数组的 引用。当左值是一个分割,而右值是一个 数组 或者对应类型的 指针 时,就会发生 复制。
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; // 错误,左值和右值的长度不同
重叠复制(Overlapping copies)会被视为错误:
s[0..2] = s[1..3]; // 错误,重叠复制
s[1..3] = s[0..2]; // 错误,重叠复制
相对于 C 的串行语义(serial semantics)来说,禁止重叠复制可以让编译器进行更为大胆的并行代码优化。
6 数组赋值(Array Setting)
如果一个分割运算符 作为 赋值表达式 的 左值 出现,并且右值的 类型 同数组元素的 类型相同,那么作为 左值 的数组的内容 将被设置为 右值 的值。
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
7 数组连接(Array Concatenation)
二元运算符“~”是 连接 运算符。它用来连接数组:
int[ ] a;
int[ ] b;
int[ ] c;
a = b ~ c; // 创建一个新的数组,方法就是将
// b 和 c 两个数组连接起来
许多语言重载了“+”运算符完成连接操作。这会产生令人费解的结果:
"10" + 3
它的值等于 13 还是字符串 "103" ?这并不是一目了然的,语言设计者不得不抖擞精神,小心地编写规则来避免 模棱两可 的情况——而这些规则难以 正确实现、容易被忽视、忘记。
让“+”仍然代表相加,而使用另一个运算符来表示数组的连接,则要好得多。
类似地,“~=” 运算符则表示 追加, 例如:
a ~= b; // a 变为 a 和 b 的连接的结果
连接操作总是会为操作数创建一个 复本,即使其中有一个操作数是 长度为 0 的数组也是一
样,因此:
a = b; // a 引用了 b
a = b ~ c[0..0]; // a 引用到 b 的一个复本
10.8 指针运算(Pointer Arithmetic)
int[3] abc; // 含有 3 个 int 型元素的静态数组
int[ ] def = [ 1, 2, 3 ]; // 含有 3 个 int 型元素的动态数组
void dibb(int* array)
{
array[2]; // 跟 *(array + 2) 相同
*(array + 2); // 获得第 3 个元素
}
void diss(int[] array)
{
array[2]; // 正确
*(array + 2); // 错误,数组不是一个指针
}
void ditt(int[3] array)
{
array[2]; // 正确
*(array + 2); // 错误,数组不是一个指针
}
10.9 矩形数组(Rectangular Arrays)
有经验的 FORTRAN 数值计算程序员都知道,对于像矩阵运算(matrix operations)的这样的事情,使用多维“矩形”数组相比通过指向 指针 的指针(这个源自语义“指向数组的指针的数组”)来访问它们要快很多。
例如,D 的语法:
double[ ][ ] matrix;
就将变量 matrix 声明为一个指向数组的 指针 的数组。(动态数组被实现为指向数组数据的指针。)
因为数组可以有不同的大小(可以动态设置大小),所以有时它被称作“锯齿(jagged)”数组。
对于代码优化来说更为糟糕的是,数组的行有时会互相指向!幸运的是,D的静态数组,使用相同的语法,被实现为固定的矩形分布:
double[3][3] matrix;
它声明了一个 3 行 3 列的矩形矩阵,在内存中连续分布。在其他语言中,它被称为多维数
组,并且声明类似于:
double matrix[3,3];
10 数组长度(Array Length)
在静态或动态数组的 [ ] 中,变量 length 被隐式的声明并设定为数组的长度。也可以使用符号“$”。
int[4] foo;
int[ ] bar = foo;
int* p = &foo[0];
// 下面这些表达式是等价的:
bar[ ]
bar[0 .. 4]
bar[0 .. length]
bar[0 .. $]
bar[0 .. bar.length]
p[0 .. length] // 'length' 未定义,因为 p 不是数组
bar[0]+length // 'length' 未定义,因为它不在 [ ] 内
bar[length-1] // 得到数组内的最后一个元素
11 数组特性(Array Properties)
静态数组的特性(properties)有:
.sizeof 返回以字节为单位的数组的大小。
.length 返回数组中元素的个数。对于静态数组来说这是一个定值。 It is of type size_t.
.ptr 返回指向数组第一个元素的指针。
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.idup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。 The copy is typed as being invariant. D 2.0 only
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。
动态数组的特性有:
.sizeof 返回动态数组引用的大小,在 32 位平台上是 8。
.length 获得/设置数组中元素的个数。 It is of type size_t.
.ptr 返回指向数组第一个元素的指针。
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.idup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。 The copy is typed as being invariant. D 2.0 only
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。
对于工作在类对象数组上的 .sort 特性,该类的定义里必须定义函数:int opCmp(Object)。它用于决定类对象的顺序。
注意:该参数是 Object 类型,而不是该类的类型。
对于工作在结构或联合数组上的 .sort 特性,该类的定义里必须定义函数:int opCmp(S)
或者 int opCmp(S*)。类型 S 是结构或者联合类型。此函数会决定排列次序。
示例:
p.length // 错误,对于指针来说 length 是未知的
s.length // 编译时间常量 3
a.length // 运行时值
p.dup // 错误,长度未知,无法复制
s.dup // 创建一个含有 3 个元素的数组,
// 将元素复制到它内
a.dup // 创建一个含有 a.length 个元素的数组,
// 同时把 a 的元素复制到它里面
11.1 设置动态数组的长度
动态数组的 .length 特性可以设置为运算符“=”的左值:
array.length = 7;
这会造成数组被在适当的位置被重新分配,现有的内容被原封不动的复制到新的数组中。
如果新的数组比原数组 短,将只保留新数组能够容纳的那些内容。
如果新数组比原数组 长,新数组长出的部分将会使用元素的默认初始值填充。
为了达到最高的效率,运行时总是试图适当地调整数组的大小以避免额外的复制工作。如果
数组不是由 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] ,它仍然同原来的
// a[3] 有重叠
为保证复制的行为,请使用 .dup 特性 来确保可以被重新调整大小的数组的唯一性。
这些讨论也同样适用于使用 ~ 和 ~= 运算符进行的连接操作。
重置动态数组的大小是一个相对昂贵的操作。所以,尽管下面这个用于填充数组的方法结果
正确:
int[ ] array;
while (1)
{ c = getinput();
if (!c)
break;
array.length = array.length + 1;
array[array.length - 1] = c;
}
它的效率也不会高。有一种更为实际的方法可以尽量减少 重新调整大小操作的 数量:
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 。
11.2 函数做为数组特性
如果函数的第一个参数是数组,那么此函数可以被当作该数组的一个特性来进行调用:
int[ ] array;
void foo(int[] a, int x);
foo(array, 3);
array.foo(3); // 表示同样的意思
12 数组边界检查(Array Bounds Checking)
如果使用 小于 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]; // 错误,越界了
应该可以在编译时通过一个 选项 决定是否 插入 动态数组越界检查代码。
//参见dmd编译开关
13 数组初始化(Array Initialization)
13.1 默认初始化(Default Initialization)
• 指针被初始化为 null。
• 静态数组的内容被初始化为数组元素类型的默认初始值。
• 动态数组被初始化为拥有 0 个元素。
• 关联数组被初始化为拥有 0 个元素。
13.2 空的初始化(Void Initialization)
当数组的 初始值 为 void 时就会发生空的(void)初始化。即表示没有进行初始化,此时数组的内容将为未定义。
这个做为效率优化是最有用的。空的初始化是一种高级技术,应该只在经剖析表明它有必要时才使用它。
10.13.3 静态数组 的 静态初始化(Static Initialization)
静态初始化由包含在 [ ] 里面的数组元素值列表实现。这些值可以可选地在它们的前面放置一个 索引值 和“:”。如果没有提供索引值,则该索引值会被设置成前一个索引值 加 1,或者 如果是第一个值,则索引值会为 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 + 1] = [ Color.blue:6, Color.green:2, Color.red:5 ];
当这些数组出现在全局域里时,它们都是静态的。否则,就需要使用 const 或 static 存储类别来对它们进行标记,让它们成为静态数组。
14 特殊数组类型
14.1 字符串(Strings)
一个字符串指的就是由多个字符组成的一个数组。而字符串字法指的仅仅是一种写成字符数
组的简单的方法。
字符串文字是不可变的(只读)。
(2.014中还是搞复杂了)
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]; // 指向第 4 个元素
char* p = str; // 指向第1 个元素
但是,因为 D 中的字符串数组不是以 0 终止的,所以如果要把一个字符串指针传递给 C
时,应该加上终止符 0 :
str ~= "\0";
或者使用函数 std.string.toStringz。
字符串的类型在编译的语义分析阶段决定。
类型是下列之一:char[ ]、wchar[ ]、dchar[ ] ,并且按照隐式转换规则决定。
如果有两个可用的等价的转换,会被认为是一个错误。为了避免出现这种模棱两可的局面,应该使用类型转换,或者使用后缀 c、w 或 d:
cast(wchar [ ])"abc" // 这是个 wchar 型的字符数组
"abc"w // 这个也是
如果需要的话,字符串文字会隐式地在 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[0]; // w 被赋值为 wchar 字符“回车”
d = 'd'; // d 被赋值为 dchar 字符 'd'
14.1.1 C 的 printf() 和 字符串
printf( ) 是一个 C 函数,它不是 D 的一部分。printf() 输出以 0 终止的 C 字符串。
有两种用 printf() 输出 D 字符串的方法。
第一种方法是加一个终止符 0 ,并将结果转换为 char* :
str ~= "\0";
printf("the string is '%s'\n", cast(char*)str);
或者:
import std.string;
printf("the string is '%s'\n", std.string.toStringz(str));
字符串文字后面已经添加有一个 0,因此可以直接使用它们:
printf("the string is '%s'\n", cast(char*)"string literal");
因此,为什么传递给 printf 的第一个字符串文字不需要进行类型转换呢?第一个参数被原型化(prototyped)成了一个 char*,而一个字符串文字可以被隐式转换到类型 char*。
但是传递给 printf 的余下实参是 个数 不定型(variadic)的(由“...”指定),而且一个字符串文字是被当作一个(长度,指针)的 组合 传递给 个数 不定型形参的。
第二种方法使用精度指示符。D 数组的布局是:数组长度、字符串,所以下面这种方法可以
工作:
printf("the string is '%.*s'\n", str);
最好的方式是使用 std.stdio.writefln,它可以处理 D 字符串:
import std.stdio;
writefln("the string is '%s'", str);
14.2 隐式转换
指针 T* 可以被隐式的转换为下面的内容:
• void*
静态数组 T[dim] 可以被隐式地转换为下列之一:
• T[ ]
• U[ ]
• void[ ]
动态数组 T[ ] 可以被隐式地转换为下列之一:
• U[ ]
• void[ ]
这里的 U 是 T 的基类。
15 关联数组(Associative Arrays)
关联数组有一个索引,此索引不必是一个整数,而且可以是少量增加(sparsely populated)。一个关联数组的索引叫做 关键字(key),而它的类型叫做 关键字类型(KeyType)。
在关联数组的声明中,关键字类型 的类型声明位于 [ ] 内:
int[char[ ] ] b; // 整型关联数组 b
// 通过一个字符数组来建立索引。
// 其 关键字类型 是 char[]
b["hello"] = 3; // 设定关键字 "hello" 对应的值为 3
func(b["hello"] ); // 把 3 做为参数传递给 func( )
关联数组里的特定关键字可以通过 remove 函数被移除:
b.remove("hello");
如果该关键字在关联数组里,则 In表达式 会产生一个指向此关联字所关联的值的指针;如
果不在,则为 null:
int* p;
p = ("hello" in b);
if (p != null)
...
关键字类型 不能是函数或者 void。
如果 关键字类型 是一个结构类型,则会使用默认机制基于结构值内的二元数据来计算
hash(哈稀值)和它的比较值。也可以通过提供下面函数做为结构成员实现自定义机制:
uint toHash( );
int opCmp(KeyType* s);
例如:
import std.string;
struct MyString
{
char[ ] str;
uint toHash( )
{ uint hash;
foreach (char c; s)
hash = (hash * 9) + c;
return hash;
}
int opCmp(MyString* s)
{
return std.string.cmp(this.str, s.str);
}
}
15.1 使用类做为关键字类型
类也可以被用作 关键字类型。为了这个可以工作,类定义必须重写(override) Object 类的下列成员函数:
• hash_t toHash( )
• int opEquals(Object)
• int opCmp(Object)
注意,opCmp 和 opEquals 的参数都是 Object 类型,而不是定义的那个类类型。
例如:
class Foo
{
int a, b;
hash_t toHash( ) { return a + b; }
int opEquals(Object o)
{ Foo f = cast(Foo) o;
return f && a == foo.a && b == foo.b;
}
int opCmp(Object o)
{ Foo f = cast(Foo) o;
if (!f)
return -1;
if (a == foo.a)
return b - foo.b;
return a - foo.a;
}
}
这些实现可以使用 opEquals 或者 opCmp 或者两者。小心以防 opEquals 和 opCmp 的
结果在它们的类对象不同时彼此混淆,或者小心相反的情况。
15.2 使用结构或联合做为关键字类型
结构或联合也可以被用作 关键字类型。想要它工作,结构或联合定义里必须定义下列成员
函数:
• hash_t toHash()
• int opEquals(S) or int opEquals(S*)
• int opCmp(S) or int opCmp(S*)
注意:opCmp 和 opEquals 的参数可以是结构或联合类型,或者是指向结构或联合类型的
指针。
例如:
struct S
{
int a, b;
hash_t toHash( ) { return a + b; }
int opEquals(S s)
{
return a == s.a && b == s.b;
}
int opCmp(S* s)
{
if (a == s.a)
return b - s.b;
return a - s.a;
}
}
这些实现可以使用 opEquals 或者 opCmp 或者两者。小心以防 opEquals 和 opCmp 的
结果在它们的结构/联合对象不同时彼此混淆,或者小心相反的情况。
15.3 特性
关联数组的特性有:
.sizeof 返回指向关联数组的引用的大小;通常值是 8 。
.length 返回关联数组中值的个数。与动态数组不同的是,它是只读的。
.keys 返回动态数组,数组的元素就是关联数组的那些关键字。
.values 返回动态数组,数组的元素就是关联数组的那些对应值。
.rehash 适当地重新组织关联数组以提高查找效率。例如,程序已经载入了符号表并将开始进行查找事,rehash 会提高效率。返回指向新数组的引用。
15.4 关联数组示例:单词统计
import std.file; // D 文件 I/O
import std.stdio;
int main (char[ ][ ] args)
{
int word_total;
int line_total;
int char_total;
int[char[ ] ] dictionary;
writefln(" lines words bytes file");
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[ ]
input = cast(char[])std.file.read(args[i]);
foreach (j, 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]++;
}
writefln("%8d%8d%8d %s", l_cnt, w_cnt, c_cnt, args[i]);
line_total += l_cnt;
word_total += w_cnt;
char_total += c_cnt;
}
if (args.length > 2)
{
writef("-------------------------------------\n%8ld%8ld%8ld total",
line_total, word_total, char_total);
}
writefln("-------------------------------------");
foreach (word; dictionary.keys.sort)
{
writefln("%3d %s", dictionary[word], word);
}
return 0;
}
[/size]
。。。。。
1 数组类别
有四种数组(arrays):
int* p; 指向数据的指针
int[3] s; 静态数组
int[ ] a; 动态数组
int[char[ ] ] x; 关联数组
1.1 指针(Pointers)
int* p;
它们是简单的指向数据的指针,等价于 C 语言的指针。
这些指针的用于提供与 C 的接口,以及用于一些特定的系统工作。由于它没有相关联的长度特性,所以对于在编译或运行时进行越界检查这类工作就没有办法。
大多数传统的指针用法可以通过使用动态数组、out 和ref 参数以及引用类型来代替。
1.2 静态数组(Static Arrays)
int[3] s;
这个类似于 C 语言的数组。静态数组与众不同的地方就是其 长度 是在编译时 固定 的。
静态数组总的大小不能超过 16Mb 。对于这样大的数组可以使用动态数组来代替。
维数为 0 的静态数组是允许的,只是不会为它分配空间。它可以用作做动态长度的 结构 的
最后一个成员,或者用作 模板扩展 的退化情况(degenerate case)。
1.3 动态数组(Dynamic Arrays)
int[ ] a;
动态数组 由数组 长度 和指向数据的 指针 组成。多个动态数组 可能 共享 数组中的全部或者部分数据。
2 数组声明(Array Declarations)
有两种声明数组的方式:前缀式 和 后缀式。前缀形式是 首选 的方法,尤其对于一些非普通
(non-trivial)类型。
2.1.1 前缀 数组声明
前缀声明出现在被声明的标志符之前,并从右至左读,于是有:
int[ ] a; // int 型动态数组
int[4][3] b; // 含有 3 个元素的数组,每个元素是含 4 个 int 的数组
int[ ][5] c; // 含有 5 个元素的数组,每个元素是 int 的动态数组
int*[ ]*[3] d; // 含有 3 个元素的数组,每个元素是指向含指向 int 的 动态数组 的 指针
int[ ]* e; // 所指元素为动态数组的指针
2.1.2 后缀数组声明
后缀声明出现在被声明的标志符之后,并且从左至右读。下面的每一组内的声明都是等价
的:
// 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++ 采用的形式完全一样,提供这种支持会给用惯了这种形
式的程序员提供一条容易的移植的途径。
3 用法(Usage)
对数组的操作可以分为两类——对数组 引用 的操作和对数组 内容 的操作。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 指向同一个数组
10.4 分割(Slicing)
分割(Slicing) 一个数组表示的是指定它的一个 子数组。一个数组分割并不会复制数据,它仅仅是该数组的 又一个引用。
例如:
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];
5 数组复制(Array Copying)
当分割运算符 作为赋值表达式 左值 出现时,意味着赋值的 目标 是数组的 内容 而不是对数组的 引用。当左值是一个分割,而右值是一个 数组 或者对应类型的 指针 时,就会发生 复制。
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; // 错误,左值和右值的长度不同
重叠复制(Overlapping copies)会被视为错误:
s[0..2] = s[1..3]; // 错误,重叠复制
s[1..3] = s[0..2]; // 错误,重叠复制
相对于 C 的串行语义(serial semantics)来说,禁止重叠复制可以让编译器进行更为大胆的并行代码优化。
6 数组赋值(Array Setting)
如果一个分割运算符 作为 赋值表达式 的 左值 出现,并且右值的 类型 同数组元素的 类型相同,那么作为 左值 的数组的内容 将被设置为 右值 的值。
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
7 数组连接(Array Concatenation)
二元运算符“~”是 连接 运算符。它用来连接数组:
int[ ] a;
int[ ] b;
int[ ] c;
a = b ~ c; // 创建一个新的数组,方法就是将
// b 和 c 两个数组连接起来
许多语言重载了“+”运算符完成连接操作。这会产生令人费解的结果:
"10" + 3
它的值等于 13 还是字符串 "103" ?这并不是一目了然的,语言设计者不得不抖擞精神,小心地编写规则来避免 模棱两可 的情况——而这些规则难以 正确实现、容易被忽视、忘记。
让“+”仍然代表相加,而使用另一个运算符来表示数组的连接,则要好得多。
类似地,“~=” 运算符则表示 追加, 例如:
a ~= b; // a 变为 a 和 b 的连接的结果
连接操作总是会为操作数创建一个 复本,即使其中有一个操作数是 长度为 0 的数组也是一
样,因此:
a = b; // a 引用了 b
a = b ~ c[0..0]; // a 引用到 b 的一个复本
10.8 指针运算(Pointer Arithmetic)
int[3] abc; // 含有 3 个 int 型元素的静态数组
int[ ] def = [ 1, 2, 3 ]; // 含有 3 个 int 型元素的动态数组
void dibb(int* array)
{
array[2]; // 跟 *(array + 2) 相同
*(array + 2); // 获得第 3 个元素
}
void diss(int[] array)
{
array[2]; // 正确
*(array + 2); // 错误,数组不是一个指针
}
void ditt(int[3] array)
{
array[2]; // 正确
*(array + 2); // 错误,数组不是一个指针
}
10.9 矩形数组(Rectangular Arrays)
有经验的 FORTRAN 数值计算程序员都知道,对于像矩阵运算(matrix operations)的这样的事情,使用多维“矩形”数组相比通过指向 指针 的指针(这个源自语义“指向数组的指针的数组”)来访问它们要快很多。
例如,D 的语法:
double[ ][ ] matrix;
就将变量 matrix 声明为一个指向数组的 指针 的数组。(动态数组被实现为指向数组数据的指针。)
因为数组可以有不同的大小(可以动态设置大小),所以有时它被称作“锯齿(jagged)”数组。
对于代码优化来说更为糟糕的是,数组的行有时会互相指向!幸运的是,D的静态数组,使用相同的语法,被实现为固定的矩形分布:
double[3][3] matrix;
它声明了一个 3 行 3 列的矩形矩阵,在内存中连续分布。在其他语言中,它被称为多维数
组,并且声明类似于:
double matrix[3,3];
10 数组长度(Array Length)
在静态或动态数组的 [ ] 中,变量 length 被隐式的声明并设定为数组的长度。也可以使用符号“$”。
int[4] foo;
int[ ] bar = foo;
int* p = &foo[0];
// 下面这些表达式是等价的:
bar[ ]
bar[0 .. 4]
bar[0 .. length]
bar[0 .. $]
bar[0 .. bar.length]
p[0 .. length] // 'length' 未定义,因为 p 不是数组
bar[0]+length // 'length' 未定义,因为它不在 [ ] 内
bar[length-1] // 得到数组内的最后一个元素
11 数组特性(Array Properties)
静态数组的特性(properties)有:
.sizeof 返回以字节为单位的数组的大小。
.length 返回数组中元素的个数。对于静态数组来说这是一个定值。 It is of type size_t.
.ptr 返回指向数组第一个元素的指针。
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.idup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。 The copy is typed as being invariant. D 2.0 only
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。
动态数组的特性有:
.sizeof 返回动态数组引用的大小,在 32 位平台上是 8。
.length 获得/设置数组中元素的个数。 It is of type size_t.
.ptr 返回指向数组第一个元素的指针。
.dup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。
.idup 创建一个同样大小的动态数组并将原数组的内容复制到新数组中。 The copy is typed as being invariant. D 2.0 only
.reverse 将数组中的元素按原来的逆序排列。返回数组。
.sort 将数组中的元素按照适当的方法排序。返回数组。
对于工作在类对象数组上的 .sort 特性,该类的定义里必须定义函数:int opCmp(Object)。它用于决定类对象的顺序。
注意:该参数是 Object 类型,而不是该类的类型。
对于工作在结构或联合数组上的 .sort 特性,该类的定义里必须定义函数:int opCmp(S)
或者 int opCmp(S*)。类型 S 是结构或者联合类型。此函数会决定排列次序。
示例:
p.length // 错误,对于指针来说 length 是未知的
s.length // 编译时间常量 3
a.length // 运行时值
p.dup // 错误,长度未知,无法复制
s.dup // 创建一个含有 3 个元素的数组,
// 将元素复制到它内
a.dup // 创建一个含有 a.length 个元素的数组,
// 同时把 a 的元素复制到它里面
11.1 设置动态数组的长度
动态数组的 .length 特性可以设置为运算符“=”的左值:
array.length = 7;
这会造成数组被在适当的位置被重新分配,现有的内容被原封不动的复制到新的数组中。
如果新的数组比原数组 短,将只保留新数组能够容纳的那些内容。
如果新数组比原数组 长,新数组长出的部分将会使用元素的默认初始值填充。
为了达到最高的效率,运行时总是试图适当地调整数组的大小以避免额外的复制工作。如果
数组不是由 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] ,它仍然同原来的
// a[3] 有重叠
为保证复制的行为,请使用 .dup 特性 来确保可以被重新调整大小的数组的唯一性。
这些讨论也同样适用于使用 ~ 和 ~= 运算符进行的连接操作。
重置动态数组的大小是一个相对昂贵的操作。所以,尽管下面这个用于填充数组的方法结果
正确:
int[ ] array;
while (1)
{ c = getinput();
if (!c)
break;
array.length = array.length + 1;
array[array.length - 1] = c;
}
它的效率也不会高。有一种更为实际的方法可以尽量减少 重新调整大小操作的 数量:
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 。
11.2 函数做为数组特性
如果函数的第一个参数是数组,那么此函数可以被当作该数组的一个特性来进行调用:
int[ ] array;
void foo(int[] a, int x);
foo(array, 3);
array.foo(3); // 表示同样的意思
12 数组边界检查(Array Bounds Checking)
如果使用 小于 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]; // 错误,越界了
应该可以在编译时通过一个 选项 决定是否 插入 动态数组越界检查代码。
//参见dmd编译开关
13 数组初始化(Array Initialization)
13.1 默认初始化(Default Initialization)
• 指针被初始化为 null。
• 静态数组的内容被初始化为数组元素类型的默认初始值。
• 动态数组被初始化为拥有 0 个元素。
• 关联数组被初始化为拥有 0 个元素。
13.2 空的初始化(Void Initialization)
当数组的 初始值 为 void 时就会发生空的(void)初始化。即表示没有进行初始化,此时数组的内容将为未定义。
这个做为效率优化是最有用的。空的初始化是一种高级技术,应该只在经剖析表明它有必要时才使用它。
10.13.3 静态数组 的 静态初始化(Static Initialization)
静态初始化由包含在 [ ] 里面的数组元素值列表实现。这些值可以可选地在它们的前面放置一个 索引值 和“:”。如果没有提供索引值,则该索引值会被设置成前一个索引值 加 1,或者 如果是第一个值,则索引值会为 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 + 1] = [ Color.blue:6, Color.green:2, Color.red:5 ];
当这些数组出现在全局域里时,它们都是静态的。否则,就需要使用 const 或 static 存储类别来对它们进行标记,让它们成为静态数组。
14 特殊数组类型
14.1 字符串(Strings)
一个字符串指的就是由多个字符组成的一个数组。而字符串字法指的仅仅是一种写成字符数
组的简单的方法。
字符串文字是不可变的(只读)。
2.014中没有这一段 char[ ] str; char[ ] str1 = "abc"; str[0] = 'b'; // 出错,"abc"是只读的,可能导致崩溃
1.030 The name string is aliased to char[], so the above declarations could be equivalently written as: string str; string str1 = "abc";
2.014 char[] str1 = "abc"; // error, "abc" is not mutable char[] str2 = "abc".dup; // ok, make mutable copy invariant(char)[] str3 = "abc"; // ok invariant(char)[] str4 = str1; // error, str4 is not mutable invariant(char)[] str5 = str1.idup; // ok, make invariant copy The name string is aliased to invariant(char)[], so the above declarations could be equivalently written as: char[] str1 = "abc"; // error, "abc" is not mutable char[] str2 = "abc".dup; // ok, make mutable copy string str3 = "abc"; // ok string str4 = str1; // error, str4 is not mutable string str5 = str1.idup; // ok, make invariant copy
(2.014中还是搞复杂了)
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]; // 指向第 4 个元素
char* p = str; // 指向第1 个元素
但是,因为 D 中的字符串数组不是以 0 终止的,所以如果要把一个字符串指针传递给 C
时,应该加上终止符 0 :
str ~= "\0";
或者使用函数 std.string.toStringz。
字符串的类型在编译的语义分析阶段决定。
类型是下列之一:char[ ]、wchar[ ]、dchar[ ] ,并且按照隐式转换规则决定。
如果有两个可用的等价的转换,会被认为是一个错误。为了避免出现这种模棱两可的局面,应该使用类型转换,或者使用后缀 c、w 或 d:
cast(wchar [ ])"abc" // 这是个 wchar 型的字符数组
"abc"w // 这个也是
如果需要的话,字符串文字会隐式地在 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[0]; // w 被赋值为 wchar 字符“回车”
d = 'd'; // d 被赋值为 dchar 字符 'd'
14.1.1 C 的 printf() 和 字符串
printf( ) 是一个 C 函数,它不是 D 的一部分。printf() 输出以 0 终止的 C 字符串。
有两种用 printf() 输出 D 字符串的方法。
第一种方法是加一个终止符 0 ,并将结果转换为 char* :
str ~= "\0";
printf("the string is '%s'\n", cast(char*)str);
或者:
import std.string;
printf("the string is '%s'\n", std.string.toStringz(str));
字符串文字后面已经添加有一个 0,因此可以直接使用它们:
printf("the string is '%s'\n", cast(char*)"string literal");
因此,为什么传递给 printf 的第一个字符串文字不需要进行类型转换呢?第一个参数被原型化(prototyped)成了一个 char*,而一个字符串文字可以被隐式转换到类型 char*。
但是传递给 printf 的余下实参是 个数 不定型(variadic)的(由“...”指定),而且一个字符串文字是被当作一个(长度,指针)的 组合 传递给 个数 不定型形参的。
第二种方法使用精度指示符。D 数组的布局是:数组长度、字符串,所以下面这种方法可以
工作:
printf("the string is '%.*s'\n", str);
最好的方式是使用 std.stdio.writefln,它可以处理 D 字符串:
import std.stdio;
writefln("the string is '%s'", str);
14.2 隐式转换
指针 T* 可以被隐式的转换为下面的内容:
• void*
静态数组 T[dim] 可以被隐式地转换为下列之一:
• T[ ]
• U[ ]
• void[ ]
动态数组 T[ ] 可以被隐式地转换为下列之一:
• U[ ]
• void[ ]
这里的 U 是 T 的基类。
15 关联数组(Associative Arrays)
关联数组有一个索引,此索引不必是一个整数,而且可以是少量增加(sparsely populated)。一个关联数组的索引叫做 关键字(key),而它的类型叫做 关键字类型(KeyType)。
在关联数组的声明中,关键字类型 的类型声明位于 [ ] 内:
int[char[ ] ] b; // 整型关联数组 b
// 通过一个字符数组来建立索引。
// 其 关键字类型 是 char[]
b["hello"] = 3; // 设定关键字 "hello" 对应的值为 3
func(b["hello"] ); // 把 3 做为参数传递给 func( )
关联数组里的特定关键字可以通过 remove 函数被移除:
b.remove("hello");
如果该关键字在关联数组里,则 In表达式 会产生一个指向此关联字所关联的值的指针;如
果不在,则为 null:
int* p;
p = ("hello" in b);
if (p != null)
...
关键字类型 不能是函数或者 void。
如果 关键字类型 是一个结构类型,则会使用默认机制基于结构值内的二元数据来计算
hash(哈稀值)和它的比较值。也可以通过提供下面函数做为结构成员实现自定义机制:
uint toHash( );
int opCmp(KeyType* s);
例如:
import std.string;
struct MyString
{
char[ ] str;
uint toHash( )
{ uint hash;
foreach (char c; s)
hash = (hash * 9) + c;
return hash;
}
int opCmp(MyString* s)
{
return std.string.cmp(this.str, s.str);
}
}
15.1 使用类做为关键字类型
类也可以被用作 关键字类型。为了这个可以工作,类定义必须重写(override) Object 类的下列成员函数:
• hash_t toHash( )
• int opEquals(Object)
• int opCmp(Object)
注意,opCmp 和 opEquals 的参数都是 Object 类型,而不是定义的那个类类型。
例如:
class Foo
{
int a, b;
hash_t toHash( ) { return a + b; }
int opEquals(Object o)
{ Foo f = cast(Foo) o;
return f && a == foo.a && b == foo.b;
}
int opCmp(Object o)
{ Foo f = cast(Foo) o;
if (!f)
return -1;
if (a == foo.a)
return b - foo.b;
return a - foo.a;
}
}
这些实现可以使用 opEquals 或者 opCmp 或者两者。小心以防 opEquals 和 opCmp 的
结果在它们的类对象不同时彼此混淆,或者小心相反的情况。
15.2 使用结构或联合做为关键字类型
结构或联合也可以被用作 关键字类型。想要它工作,结构或联合定义里必须定义下列成员
函数:
• hash_t toHash()
• int opEquals(S) or int opEquals(S*)
• int opCmp(S) or int opCmp(S*)
注意:opCmp 和 opEquals 的参数可以是结构或联合类型,或者是指向结构或联合类型的
指针。
例如:
struct S
{
int a, b;
hash_t toHash( ) { return a + b; }
int opEquals(S s)
{
return a == s.a && b == s.b;
}
int opCmp(S* s)
{
if (a == s.a)
return b - s.b;
return a - s.a;
}
}
这些实现可以使用 opEquals 或者 opCmp 或者两者。小心以防 opEquals 和 opCmp 的
结果在它们的结构/联合对象不同时彼此混淆,或者小心相反的情况。
15.3 特性
关联数组的特性有:
.sizeof 返回指向关联数组的引用的大小;通常值是 8 。
.length 返回关联数组中值的个数。与动态数组不同的是,它是只读的。
.keys 返回动态数组,数组的元素就是关联数组的那些关键字。
.values 返回动态数组,数组的元素就是关联数组的那些对应值。
.rehash 适当地重新组织关联数组以提高查找效率。例如,程序已经载入了符号表并将开始进行查找事,rehash 会提高效率。返回指向新数组的引用。
15.4 关联数组示例:单词统计
import std.file; // D 文件 I/O
import std.stdio;
int main (char[ ][ ] args)
{
int word_total;
int line_total;
int char_total;
int[char[ ] ] dictionary;
writefln(" lines words bytes file");
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[ ]
input = cast(char[])std.file.read(args[i]);
foreach (j, 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]++;
}
writefln("%8d%8d%8d %s", l_cnt, w_cnt, c_cnt, args[i]);
line_total += l_cnt;
word_total += w_cnt;
char_total += c_cnt;
}
if (args.length > 2)
{
writef("-------------------------------------\n%8ld%8ld%8ld total",
line_total, word_total, char_total);
}
writefln("-------------------------------------");
foreach (word; dictionary.keys.sort)
{
writefln("%3d %s", dictionary[word], word);
}
return 0;
}
[/size]
。。。。。
发表评论
-
土耳其文《d编程》range 翻译 一
2011-11-15 02:01 1507Ranges 范围 Ranges are an abstra ... -
土耳其文《d编程》range 翻译 二
2011-11-15 01:59 1006As you can see, that output doe ... -
d2 range 和 标准C++中的Iterator(迭代器)简介
2011-05-07 12:59 2117原文: http://hi.baidu.com/c ... -
三访安德烈Alexandrescu(第2部)
2010-08-20 12:53 1422Google翻译哦 面试------> 应翻成 访谈 ... -
三访安德烈Alexandrescu(第一部分)
2010-08-20 12:43 1349google翻译哦 Interview with Andre ... -
Garden Editor project 日记 之二 10.16 ---
2009-10-16 02:39 02009.10.16 T[new] misgivings ... -
Garden Editor project 日记 之一 09.09.25 --- 10.15
2009-09-24 22:56 0kill two birds with one stone, ... -
template metaprogramming 9
2009-09-09 16:08 1169原文:https://docs.google.co ... -
Floating Point in D (2.030 新)
2009-05-12 23:27 20485.17 23:50 更新 5.16 20:30 翻译更 ... -
Migrating to Shared (2.030 新)
2009-05-12 23:03 11545.19 0:10 更新(完成) ... -
D 2.0 的gc
2008-12-04 19:53 1251http://lucifer1982.wordpress.co ... -
垃圾回收 2.014
2008-06-10 07:20 983无版本差异 D 是一种全面采用垃圾回收(Garbage Co ... -
类 class 2.014
2008-06-09 22:51 1085D 的面向对象的特性都来源于类。类层次里的顶层是 Object ... -
接 口 2.014
2008-06-09 22:51 843接口声明: interface 标 ... -
C 语言接口 2.014
2008-06-09 22:50 1029D 的设计就是要在目标系统上能够很好地符合于 C 编译器。D ... -
Traits 特征 2.014
2008-06-07 11:25 12466.14 翻译 (d语言的反 ... -
常量和不变量 Const and Invariant 2.014
2008-06-07 11:22 1312请参考: D 2.0 Const/Final/Invarian ... -
词法 2.014
2008-06-07 10:22 1501在 D 中,词法分析独立于语法分析和语义分析。词法分析器是将源 ... -
枚 举 2.014
2008-06-07 08:41 1155枚举声明: enum 枚举标记 枚举体 enum 枚举体 en ... -
函 数 2 (2.014)
2008-06-07 08:22 10327 嵌套函数 函数可以被 ...
相关推荐
E3 2.014版的DPU升级教程将帮助用户了解整个过程。 首先,我们需要理解DPU是什么。DPU,全称为数字处理单元,是设备中负责处理特定任务的微处理器。在魅族音乐播放器中,DPU可能负责音频解码、信号处理等关键功能。...
在Web开发领域,JavaScript是一种至关重要的编程语言,它在【标题】"Web-programming-languages:Web 编程语言 (CS 6301.014) 作业和项目"以及【描述】中被特别提及,暗示了课程或资料的重点。JavaScript主要用于构建...
首先,标题《灾后恢复生产管理程序2.pdf》和描述“灾后恢复生产管理程序2.pdf”明确指出了该文档的主题是关于灾后生产恢复的管理程序。在IT行业中,灾后恢复(Disaster Recovery,简称DR)是一个关键领域,它涉及到...
2. 非常低的功耗:在1Hz的湿度、气压和温度测量模式下为3.6µA,而在不同的操作模式下,气/压/温/湿/气体的功耗在0.1mA至16mA之间变化。 3. 小巧的3.0mm×3.0mm的封装尺寸,高度为1.0mm。 4. 广阔的电源电压范围:...
所以2014万米2等于2.014×10^6米2。 3. 俯视图:第三题涉及几何图形的投影,俯视图是从物体上方看下去的投影,这里需要识别出给定图形的俯视图形状。 4. 二次根式的除法、合并同类项、幂的乘方、完全平方公式:这...
2. **填数游戏**:如003题,需要孩子找到一组能相互配对的数字,使得每个差值都是4。这锻炼了他们的计算能力和寻找模式的能力。 3. **等式求解**:题目004和006涉及到简单的代数概念,孩子们需要理解等式的平衡性,...
**2. 十六进制整数** - **定义**: 十六进制整数是以`0x`或`0X`开头的数字序列。 - **例子**: `0x44`表示十进制中的44,而`0X134`表示十进制中的134。 - **错误形式**: `X4f`和`04C`都不是有效的十六进制表示。 ...
2. **竞赛内容**: - 参赛者需在6小时内完成两个赛件的加工,使用现场提供的设备、软件、工具等。 - 完成赛件加工后,还需进行自我检测,选择合适的量具填写检测结果。 - 职业素养考核包括设备操作规范、工具使用...
014. 找规律填数的题目要求孩子观察序列并找出其中的规律,然后进行填空。 015. 这些问题考察的是在有序集合中确定位置的能力,通过已知位置推算其他位置的数量。 016. 将加法算式改写为乘法算式,训练孩子对乘法...
2. **太极成交量 TJCJL**:ZZ:=IF(REF(C,1)>REF(O,1) AND O>REF(C,1)*1.014 AND C*1.02,1,3),这个条件判断语句用来识别某种特定的K线形态,当满足前一天收盘价高于开盘价,开盘价高于前一天收盘价的1.014倍,且收盘...
实例131 对数组元素进行随机排序 160 实例132 随机抽取数组中元素 161 实例133 二维数组的输出 162 实例134 获取数组当前的键名和值 162 实例135 检测数组中是否存在某个值 163 实例136 获取数组中的当前单元 164 ...
实例014 OutLook界面 11 实例015 带导航菜单的主界面 12 实例016 图形化的导航界面 14 1.5 特色程序界面 15 实例017 隐藏式窗体 15 实例018 类似Windows XP的程序界面 18 实例019 软件启动界面 19 实例020 以...
2. 物理量的测量和精确度:内容中提到了“0004mm”、“004.0mm2”、“0.014(mm)”等,这可能与测量工具(如游标卡尺、螺旋测微器等)的精度和使用方法,以及如何减小误差和提高测量精度有关。 3. 物理实验的基础...
例如,计算2.14乘以5,11.8乘以6,30.014乘以7等,以及判断乘积的小数位数,如0.3乘以30,0.89乘以19等。这些练习有助于学生巩固所学知识,并学会如何在没有计算的情况下预测结果的位数。 此外,还有填空题,比如...
1. ZZ:如果前一天收盘价高于开盘价,且开盘价高于前一天收盘价的1.014倍,同时当天收盘价低于开盘价的1.02倍,则ZZ设为1;否则,ZZ设为3。这通常表示市场出现一定幅度的波动,可能预示着趋势的变化。 在stickline...
阈值`0.014`是根据实际情况调整的,用于控制去噪的程度。 完成去噪后,通过`waverec`函数对处理过的系数进行小波重构,得到去噪后的信号`s0`。最后,使用`plot`函数绘制原始信号和去噪后的信号,以便于比较和可视化...
2. **控制结构**: - 实例005的三数排序和068的旋转数列,涉及到条件判断和循环结构。 - 实例029的反向输出和032的反向输出II,展示了列表的遍历和反转技巧。 3. **函数与模块**: - 实例034的调用函数和044的...
o029_snow.zip 一个雪花飘落例子(3KB) 626,o028_zoomsys.zip 类似画图放大镜的东西(2KB) 627,o027_wiz2.zip 制作应用程序向导的例子(类似安装程序的界面)!(5KB) 628,o025_StopWatch.zip “跑表...