- 浏览: 223361 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
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 1535Ranges 范围 Ranges are an abstra ... -
土耳其文《d编程》range 翻译 二
2011-11-15 01:59 1030As you can see, that output doe ... -
d2 range 和 标准C++中的Iterator(迭代器)简介
2011-05-07 12:59 2148原文: http://hi.baidu.com/c ... -
三访安德烈Alexandrescu(第2部)
2010-08-20 12:53 1437Google翻译哦 面试------> 应翻成 访谈 ... -
三访安德烈Alexandrescu(第一部分)
2010-08-20 12:43 1359google翻译哦 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 1187原文:https://docs.google.co ... -
Floating Point in D (2.030 新)
2009-05-12 23:27 20675.17 23:50 更新 5.16 20:30 翻译更 ... -
Migrating to Shared (2.030 新)
2009-05-12 23:03 11685.19 0:10 更新(完成) ... -
D 2.0 的gc
2008-12-04 19:53 1264http://lucifer1982.wordpress.co ... -
垃圾回收 2.014
2008-06-10 07:20 993无版本差异 D 是一种全面采用垃圾回收(Garbage Co ... -
类 class 2.014
2008-06-09 22:51 1111D 的面向对象的特性都来源于类。类层次里的顶层是 Object ... -
接 口 2.014
2008-06-09 22:51 857接口声明: interface 标 ... -
C 语言接口 2.014
2008-06-09 22:50 1043D 的设计就是要在目标系统上能够很好地符合于 C 编译器。D ... -
Traits 特征 2.014
2008-06-07 11:25 12736.14 翻译 (d语言的反 ... -
常量和不变量 Const and Invariant 2.014
2008-06-07 11:22 1325请参考: D 2.0 Const/Final/Invarian ... -
词法 2.014
2008-06-07 10:22 1515在 D 中,词法分析独立于语法分析和语义分析。词法分析器是将源 ... -
枚 举 2.014
2008-06-07 08:41 1169枚举声明: enum 枚举标记 枚举体 enum 枚举体 en ... -
函 数 2 (2.014)
2008-06-07 08:22 10487 嵌套函数 函数可以被 ...
相关推荐
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涉及到简单的代数概念,孩子们需要理解等式的平衡性,...
实例131 对数组元素进行随机排序 160 实例132 随机抽取数组中元素 161 实例133 二维数组的输出 162 实例134 获取数组当前的键名和值 162 实例135 检测数组中是否存在某个值 163 实例136 获取数组中的当前单元 164 ...
**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倍,且收盘...
实例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 “跑表...