译者:uframer
模板
D 模板提供了一种提供范型编程和偏特化能力的简洁的方法。
关联数组
关联数组是索引可以为任意类型的数组,不像普通数组那样必须使用整数作为索引。本质上,关联数组就是散列表。关联数组使构建快速、高效、无错的符号表变得容易了。
真正的 typedef
C 和 C++ 的 typedef 实际上是类型 别名 ,因为它不会引入新的类型。D 实现了真正的 typedef :
typedef int handle;
实实在在地创造了一个新类型 handle 。D 同样会对 typedef 引入的类型进行类型检查,并且 typedef 也参与函数重载的决策。
例如:
int foo(int i);
int foo(handle h);
Bit 类型
机器上最基本的数据类型是位,因此 D 直接支持 bit 数据类型。它在创建位数组时最有用:
bit[] foo;
函数
如你所愿,D 提供常规的对函数的支持,包括全局函数、重载函数、函数在线化、成员函数、虚函数、函数指针等等。另外,D 还支持:
嵌套函数
函数可以嵌套在其他函数内。这对于代码分解、局部性以及函数闭包技术都具有很高的价值。
函数文字量
匿名函数可以直接嵌入到表达式中。
动态闭包
嵌套函数和类成员函数可以被称为闭包(也被称为委托),它们可使范型编程更为容易并保证类型安全。
In, Out 和 Inout 参数
这几个修饰符不只能使函数更为易于理解,还能避免使用指针而不会影响代码的功能,另外这也会提高编译器帮助程序员找到编码问题的可能性。
这些修饰符使 D 能够直接同更多的外部 API 对接。也就无需使用“接口定义语言”(IDL)之类的东西了。
数组
C 数组有几个可以被纠正的缺点:
* 数组本身并不带有数组结构的信息,它们必须另外存储和传递。一个经典的例子就是传递给 main(int argc, char *argv[]) 函数的 argc
和 argv 参数。(在 D 中,main 被声明为 main(char[][] args) 。)
* 数组不是一等公民。当一个数组被传递给函数时,他被转换为指针,尽管那个原型令人迷惑地声称它是一个数组。当发生类型转换时,所有的数组类型信息也就丢失了。
* C 数组的大小不可改变。这意味着即使最简单的聚集如堆栈都必须用一个复杂的类构造。
* C 数组没有边界检查,因为它们根本不知道数组边界是多少。
* 数组声明中的 [] 位于标志符之后。这使得声明如一个指向数组的指针这样的东西都需要复杂难懂语法:
int (*array)[3];
在 D 中,数组的 [] 位于左侧:
int[3] *array; // 声明了一个指向含有 3 个 int 的数组的指针
long[] func(int x); // 声明了一个返回含有 long
数据的数组
显然这更易于理解。
D 数组有四种变体:指针、静态数组、动态数组和关联数组。
字符串
在 C 和 C++ 中,对字符串的操作是如此的频繁,而又如此的笨拙,以至于最好还是由语言本身来支持它比较好。现代语言都处理字符串连接、复制等等操作,D
语言也提供了这些支持。字符串是直接由经过改进的数组来处理的。
资源管理
垃圾收集
D 的内存分配完全采用垃圾收集。经验告诉我们,C++ 中的很多复杂特征都是用于处理内存释放的。有了垃圾收集,语言就变得简单多了。
有一种看法认为垃圾收集是给那些懒惰、初级的程序员准备的。我还记得那些对 C++ 的评论,毕竟,没有什么 C++ 能做而 C 不能做的,或者这对汇编来说也一样。
采用垃圾收集可以避免 C 和 C++ 中必需的乏味的、易于出错的内存分配和追踪代码。这不只意味着更少的开发时间和更低的维护费用,还意味着程序运行得更快!
当然,可以在 C++ 中使用垃圾收集程序,我已经在我自己的项目中使用它了。C++ 对垃圾收集程序并不友好,这也造成了 C++
中垃圾收集的低效。很多运行时库的代码都不能同来垃圾收集程序一同工作。
显式内存分配
尽管 D 是一种采用垃圾收集的语言,还是可以重写某个类的 new 和 delete 操作以采用一个定制的分配器。
RAII
RAII 是一种管理资源分配和释放的现代软件开发技术。D 以一种可控的、可预测的方式支持 RAII ,它是独立于垃圾收集程序的回收周期的。
性能
轻量级聚集
D 支持简单的 C 风格的结构,既保证了对 C 数据结构的兼容性,也是因为有时采用类有杀鸡用牛刀之嫌。
内联汇编
设备驱动程序、高性能系统程序、嵌入式系统和某些特殊的代码需要使用汇编语言完成任务。尽管 D
的实现不一定要实现内联汇编,它也仍被定义为语言的一部分。他可以满足绝大多数使用汇编语言的需要,这样就不需要单独的汇编程序或者使用 DLL 了。
许多的 D 实现同时也实现那些类似于 C 的支持 I/O 端口操作、直接访问浮点硬件等内部功能的内函数。
可靠性
现代的语言应该竭尽所能地帮助程序员避免出错。语言提供的帮助有多种形式:从易于使用更为健壮的技术,到有编译器指出明显出错的代码,到运行时检查。
契约
契约式编程(由 B. Meyer 发明)是一种用于保证程序正确性的革命性的技术。D 版本的 DBC 包括函数先验条件、函数后验条件、雷不变量和断言契约。
单元测试
可以给一个类加入单元测试,这样测试程序就能在程序启动时自动运行。这样就能够在每次构建时都验证类是否实现了他所应完成的功能。单元测试构成了源代码的一部分。创建单元测试成为了类开发过程中的自然的一部分,而不是将完成的代码直接抛给测试小组。
单元测试可以使用其它语言完成,但是其结果看起来有一种七拼八凑的感觉,而且你采用的那种语言很可能并不兼容这个概念。单元测试是 D
的一个主要特征。对于库函数来说,单元测试已经被证明是十分有效的。它既可以保证函数工作正常,也可以演示如何使用这些函数。
考虑大量的可以从网上下载的 C++ 库和应用程序代码。其中有“几个”是带有验证测试的?更不要奢望单元测试了?少于 1%
?通常的做法是,如果它们能通过编译,我们就假定它是正确的。而且我们不知道变异过程中给出的警告到底是真正的错误还是瞎唠叨。
契约式编程和单元测试使 D 为编写可信赖、健壮的系统程序的最好的语言。单元测试还是我们能够粗略但快速地估计你从未经手的 D
代码片断的质量——如果没有单元测试和契约式编程,每人会干这种事。
调试特征和语句
现在调试成为了语言语法的一部分了。可以在编译时决定是否使用这些代码,再也不用使用宏或者预处理命令了。调试语法提供了一种持续的、可移植的、易于理解的识别调试代码的方法,使程序员既能够生成带有调试代码的二进制版本,也能够生成没有调试代码的二进制版本。
异常处理
D 采用了更为高级的 try-catch-finally 模型而不是原来的 try-catch 模型。没有必要只是为了利用析构函数实现 finally
语义而构造一个傀儡对象。
同步
因为多线程编程已经越来越成为主流,所以 D 提供了构建多线程程序的原语。同步既可以作用在方法上,也可以作用在对象上。
synchronized int func() { . }
同步方法一次只允许一个线程执行。
同步语句将在语句块周围插入一个互斥体,控制对象或全局的访问。
对健壮性技术的支持
* 使用动态数组而不是指针
* 使用对变量的引用而不是指针
* 使用对对象的引用而不是指针
* 使用垃圾收集而不是显式内存分配
* 内建线程同步原语
* 不再有宏给你的代码来那么一下子
* 使用内联函数而不是宏
* 在很大程度上减少了使用指针的需要
* 整型的大小是明确的
* 不用再担心 char 类型是否有符号了
* 不必再分别在源文件和头文件中重复地写声明了
* 为调试代码提供了显式的解析支持
编译时检查
* 更强的类型检查
* 需要进行显式初始化
* 不允许出现未使用的局部变量
* 不允许出现空的(只由‘;’的)循环体
* 赋值语句不会返回布尔类型的结果
* 废弃过时的 API
运行时检查
* assert() 表达式
* 数组边界检查
* switch 语句中的未定义 case 语句异常
* 内存耗尽异常
* in、out 和类不变量提供了对契约式编程的支持
兼容性
运算符优先级和求值规则
D 保留了 C 的运算符和它们的优先级、求值的规则和类型提升规则。这就避免了由于同 C 的语义不同而造成的微妙的难以发现的 bug 的出现。
直接访问 C API
D 不支拥有同 C 类型对应的类型,它还提供了直接访问 C 函数的能力。完全没有必要编写封装函数和参数变换器,也没有必要逐一地复制聚集类型的成员。
支持所有的 C 数据类型
使对 C API 或者现有的 C 库代码的接口成为可能。D 支持结构、联合、枚举、指针和所有的 C99 类型。D
还拥有设置结构成员对齐方式的能力,这样就可以保证同外部导入的数据格式的兼容。
操作系统异常处理
D 的异常处理机制将在应用程序中利用底层操作系统提供的异常处理方式。
使用现成的工具
D 生成标准的目标文件格式,这样就能够使用标准的汇编程序、链接器、调试器、性能分析工具、可执行程序压缩程序和其他的分析程序,还能够同其他语言编写的代码相链接。
项目管理
版本控制
D 对从同一份源码生成多个版本的程序提供了内建的支持。它替代了 C 预处理程序的 #if/#endif 技术。
废弃
随着代码不停的演进,一些旧的库代码会被更新、更好的版本代替。同时旧的版本必须可用以支持旧的客户代码,旧的版本可以被标记为 废弃的
。可以通过编译器的一个选项来判断使用废弃版本代码的版本是否是合法的,这样一来负责维护的程序员就可以更为轻松的判断哪里是依赖于已经被废弃的特征的。
没有警告
D
编译器不会为可疑的代码生成警告。代码要么可以被编译器接受,要么不能被编译器接受。这样就不会引起有关哪些警告确实是错误而那些不是的争论,也就没有如何处理它们的争论了。对编译器警告的需求本身就是不好的语言设计的表现。
示例程序 (sieve.d)
/* 用Eratosthenes(埃拉托色尼,公元前三世纪的希腊天文学家、数学家和地理学家)筛法求素数 */
bit[8191] flags;
int main()
{
int i, count, prime, k, iter;
printf("10 次迭代\n");
for (iter = 1; iter <= 10; iter++)
{
count = 0;
flags[] = 1;
for (i = 0; i < flags.length; i++)
{
if (flags[i])
{
prime = i + i + 3;
k = i + prime;
while (k < flags.length)
{
flags[k] = 0;
k += prime;
}
count += 1;
}
}
}
printf ("\n%d 个素数", count);
return 0;
}