D 语言简介

内容概要
曾经一度研究过D语言,这是当时写的一篇介绍D语言的文章。

【欢迎转载,请注明作者:燕良,原文出处:游戏程序员的自我修养

本文发表于《游戏创造》杂志2007年第10期

横空出世的 D 语言

如果我说我对于 C++是“爱恨交织”,相信能够赢得很多战斗在一线的 C++程序员的共鸣。C++一直以其强大、高效等特性统治着游戏开发领域,然而它并不完美。有没有可能构建一种新的语言,既能具有 C++强大、灵活、高效的特点,又能够象 Java,Ruby 等现代编程语言那样兼顾开发效率呢?OK!不是你一个人在这样想,而且早已经有位牛人这样做了,他就是 Walter Bright,而这个语言就是 D 语言。

Walter Bright 本身就是 C++界的大牛,从上世纪 80 年代开始一直从事 C++编译器的开发。C++在语法上需要兼容 C,而且 C++本身也变得越来越复杂,开发出一个高效率的 C++ 编译器难度非常大,在 1999 年末,Walter Bright 决定抛掉向后兼容的包袱,萃取 C++的精华,结合 Ruby,Python,Java 等现代语言的特点,设计一种全新的编程语言――D 语言,并于 2001 年 12 月发布了第一个 Alpha 版 D 语言编译器――DMD。

D语言还处在发展阶段,但是它已经渐渐流行开来,根据TIOBE的编程语言调查(http://www.tiobe.com/tpci.htm),D语言已经上升到第 13 位,目前它的普及率为 1.37%(C++为 9.938%,排名第 5;Ruby为 2.065%,排第 10 名)。D语言的今年上升势头很猛,不亚于Ruby,十分抢眼。具我所知,目前完全使用D语言开发的商业软件还很少,但是D语言社区发展的非常迅速,在D语言开源社区dsource网站上有超过一百个项目在开发,包括服务器、开发工具、GUI等各种基础库、游戏项目等。另外,D语言也赢得了一些C++牛人的关注和支持,例如《Imperfect C++》一书的作者Matthew Wilson就是D语言news group的常客。

What is D?

D 语言总体上说是比 C++稍高层的一个语言。它适合于底层系统,以及象游戏这样对效率要求很高的应用程序的开发。它和 C++一样,是一种编译型语言,D 语言的源代码需要象C++源代码一样,通过编译、链接,最终形成本机代码。D 语言不运行于任何的虚拟机之上,它保持了和 C++一样访问操作系统 API 以及硬件的能力!

D 语言仍然属于 C 语系,它的代码看上去很象 C++或 Java,但是它在语法上不兼容 C语言。这是一个大胆的设计,想当年,C++的成功很大程度上得益于它完全兼容 C 语法,这不单单是可以使得C程序员有一个平滑的学习曲线,更重要的是大量的C代码库可以为C++所用!D 语言为了设计一套“干净”的语法,在源代码级完全不兼容 C,但是它保持了在二 进制级与 C 的兼容。这是一个非常巧妙的设计!以全新的语法为基础,D 语言对于编译器的开发者也是友好的,这样直接的结果就是 D 语言的编译速度非常之快!与 C 语言的二进制兼容,D 语言代码可以方便的调用操作系统 API,以及其它 C 语言编写的库。

如果只有整洁的语法设计,高效的编译器,那当然不足成为 D 语言流行的理由。为了提高开发效率,D 语言加入的一个主要特性就是“垃圾收集(Garbage Collection)”。另外,为了适应大型项目的团队协作开发,D 语言还增加了“契约式编程”、“单元测试”、“版本控制”等概念和工具的支持。

初试 D 语言

说了这么多,还是看点实际的吧。☺ 下面先让我们一起完成一个标准的“Hello World!” 程序。

首先需要配置一下 D 语言的基本开发环境(以 Windows 平台为例):

  1. 从“http://www.digitalmars.com/d/dcompiler.html”下载一个最新版的 DMD 编译器 “ http://ftp.digitalmars.com/dmd.zip ”, 以 及 链 接 器 “http://ftp.digitalmars.com/dmc.zip”;
  2. 把 dmd.zip 和 dmc.zip 解压到一个目录中,例如“C:\dmd”;
  3. 为了方便调用,把 dmd.exe 所在目录加到系统环境变量――PATH 中,例如“C:\dmd\dmd\bin”;

OK,就这么简单。接下来,打开一个任意的文本编辑器,键入以下代码:

import std.stdio;
void main()
{
writefln("Hello world!");
}

保存为“hello.d”。

打开 Windows 控制台,进入你保存“hello.d”文件的目录,然后键入命令:

dmd hello.d

然后键入“hello”即可执行我们刚才的编写的程序――在屏幕上打印“Hello world!”。

是不是很象 C 语言?看一下两个与 C 语言不同之处:

  1. 第一行“import std.stdio”:引入 D 语言的标准 io 库, 它是”dmdlibphobos.lib”的一部分; 库文件的查找由 sc.ini 中的 LIB 环境变量指定;
  2. writefln 函数:在 D 语言标准库中用来替代 C 语言的“printf”的函数,主要是增强了类型安全性。

DMD 编译器压缩包中带有一个 MS Windbg 程序,可以用来调试。为了生成调试信息,需要使用“-g”参数来编译:

dmd hello.d –g

然后键入控制台命令“windbg hello.exe”启动调试器,然后在调试器中键入“g _Dmain”命令即可开始调试。

Windbg 程序不支持 unicode 格式的源码文件(D 语言支持这点),我们也可以使用 VisualC++来调试 D 语言生成的 exe 程序。别忘了,D 语言在二进制上与 C 兼容。使用 Visual C++调试的步骤如下:

  1. 建立一个空的 Visual C++项目,项目名称任意;
  2. 设置项目属性:“配置属性=>调试=>命令”,将其设为你要调试的编译好的 exe 文件,例如“d:\work\bin\hello.exe”;如果需要的话还可以设置“工作目录”;
  3. 然后在 Visual studio 中打开相应的 D 源码文件;
  4. 设置断点,按 F5 启动,就这么简单!

这种方法跟踪程序流程没什么问题,不过 D 中的大部分变量还是无法正确显示出内容。随着 D 社区的发展,相信以后会有支持 D 语言的好的 IDE 出现。

下面我们通过一个稍微复杂的例子来看一下 D 语言的面向对象编程具体是什么样子。

下面这个小程序有一个虚基类 Shape,它有一个虚函数用来计算面积,然后有两个派生类,分别是 Rect 和 Sphere,请看一下下面的代码:

import std.stdio;
void main()
{
Rect rc = new Rect;
rc.Width = 2;
rc.Height = 3;
writefln(", area = ", rc.calArea());
//--
Shape shapeArray[2];
shapeArray[0] = rc;
shapeArray[1] = new Circle;
foreach(Shape shape; shapeArray)
writefln("Area = ", shape.calArea());
//--
writefln("sizeof Shape = ", Shape.sizeof,
", sizeof Rect = ", Rect.sizeof,
", sizeof Circle = ", Circle.sizeof);
}
class Rect : Shape
{
this()
{
writefln("Rect.ctor");
super();
}
~this() { writefln("Rect.dtor"); }
override int calArea()
{ return m_width*m_height; }
//-- properties
int Width() { return m_width;}
int Height() { return m_height;}
void Width(int w) { m_width = w;}
void Height(int h) { m_height = h;}
private:
int m_width,
m_height;
}
class Circle : Shape
{
this() { writefln("Circle.ctor");}
~this() { writefln("Circle.dtor");}
override int calArea()
{ return cast(int)(3.1415926f*m_radius*m_radius);}
//-- properties
int Radius() { return m_radius;}
void Radius(int r) { m_radius = r;}
private:
int m_radius;
}
abstract class Shape
{
this() { writefln("Shape.ctor");}
~this() { writefln("Shape.dtor");}
int calArea();
}

请注意以下这些要点:

  1. D 源程序不再需要象 C/C++那样分为头文件(.h)和源文件(.c,.cpp);也不再需要前向声明(forward declaration)、extern 声明等机制,程序员不必再恪守“先声明才能使用”的规则,这些都由编译器去操心;
  2. D 支持 unicode 格式的源码文件,如果你的程序中写了中文,请将它保存为 unicode 格式;
  3. 和 C++一样,D 的面向对象也是通过 class 来实现,但是 D 不支持多继承,只支持interface 的多继承;所有的 class 都最终继承于内建的 Object 类;这点和 Java 非常相似;
  4. 所有的类对象都是通过 引用的方式来实例化的, 垃圾收集机制负责资源清理工作;在发生处理异常的时候,不必再担心资源释放的问题了;
  5. class 的成员函数无需声明为“virtual”,所有的成员函数都可以被 override,编译器会自动识别,并生成虚函数表;
  6. 类成员都通过“.”操作符来访问,不再需要“->”操作符;
  7. D 语言中的类型都有一些固有的属性,在上面的例子中演示了“sizeof”的用法,类似的属性还有“min”,“max”等;
  8. D 支持 foreach 语法,上例 main 函数中的 foreach 循环也可以写成:
     for(int i=0; i<shapeArray.length; i++)
     writefln("Area = ", shapeArray[i].calArea());
    
  9. D 语言中类的构造函数和析构函数的函数名都为“this”;
  10. D 语言中通过 super()函数来调用基类构造函数,这样派生类可以控制基类构造函数的调用时机;
  11. D 语言中的变量都会初始化为一个默认值;
  12. D 语言的 class 和 struct 支持属性(Properties),例如 Sphere 的 Radius;

怎么样?是不是感觉很简单?D 语言的易学易用也是其作者考虑的一个重点,对于有一定 C/C++编程基础的人来说还是很容易上手的!

另外,一些我们常用的 C++特性,例如操作符重载、模板、异常、内联汇编等等,都被 D 语言继承了下来,对于用惯 C++的人来说很少会有什么遗憾。:smile:

D 语言进阶特性

通过前面的例子,相信你已经对 D 语言有了一个形象的认识。下面看一下 D 语言的一些其它的特性。

模块(Modules )

在 D 语言中,模块和源文件是一一对应的,模块名就是去掉路径和扩展名的文件名。在 D 中不支持 C++的 namespace,模块也有类似的作用,每个模块自动产生一个 namespace。模块通过“module”关键字来声明,通过“import”关键字来导入。不同于文件包含,import导入的是符号,而不是 C/C++那样的导入源代码,所以不用担心模块被多次导入,也就是说不用象 C/C++那样使用#ifdef/#endf 把头文件包起来(或者使用#pragma once),这也大大加 快了编译速度。

模块默认是 private import,也就是说,如果模块 A 导入了模块 B,而 B 中导入了 C,那么在 A 中是无法访问 C 的内容的。可以通过“public import”来使得在 A 中可以访问 C。

为了方便书写,D 语言还提供了 renamed import,例如“import io = std.stdio;”,我们可以这样访问其中的函数了――“io.writefln()”。

以我们前面的 Shape 小程序为例,我们可以把 Rect,Circle,Shape 三个类的代码放到一个“MyShape.d”文件中,并在前面加上一句“module MyShape;”;然后把 main 函数放入一个“MyMain.d”文件中,并在其中加入一句“import MyShape;”,然后使用“dmd MyShape.d MyMain.d”命令来编译。

函数委托(Delegate )

D 语言中支持函数指针,不支持成员函数指针,但是支持更方便的方式--委托!例如,在前面的 Shape 例子中的 main 函数中可以加入以下代码:

int delegate() dg;
dg = &rc.calArea;
writefln("call delegate, return = ", dg());

契约式编程(Contract)

D 语言内部提供了一些工具来支持契约式编程。所谓契约,其实很简单,它判断表达式的值必须为真,我们以前常用的 ASSERT 就是一个最基本的契约。D 中支持 assert 关键字,但是与 C 不同,它会抛出一个 AssertException 异常。

通过 in,out,body 关键字可以对函数的参数和返回值进行验证,语法请参考下面的例子:

int myFunc(int a)
in
{
writefln("myFunc.in");
}
out(result)
{
writefln("myFunc.out, result = ",a);
}
body
{
a += 10;
return a;
}

而 D 语言的 class 可以声明一个“invariant”成员,用来描述这个类的一些固定不变的特性,invariant 代码在类的实例构造之后、析构之前、public 函数的调用时和调用完成后被自动调用,请看下面的这个小程序。

import std.stdio;
void main()
{
MyClass obj = new MyClass();
obj.foo();
}
class MyClass
{
float a;
this() { a=0;}
~this() {}
void foo() { writefln("MyClass.foo");}
invariant
{
assert(a*a >= 0);
writefln("MyClass.invariant");
}
}

单元测试

D 语言通过内建的单元测试机制,可以轻松实现自动化的单元测试,以及方便的测试代码管理。类似于前面的“invariant”成员,class 还可以拥有“unittest”成员。 使用 dmd 的”-unittest”命令行参数将 unittest 代码编译进最终的可执行文件;unittest 代码在静态构造之后在 main 之前被自动调用。下面是 D 语言文档中的一个例子:

class Sum
{
int add(int x, int y) { return x + y; }
unittest
{
Sum sum = new Sum;
assert(sum.add(3,4) == 7);
assert(sum.add(-2,0) == -2);
}
}

相关社区和资源

笔者学习 D 语言时间也不长,小小文章难以很深入的介绍。希望您看了本文之后对 D 语言能够产生兴趣,更多信息请参考以下网站:

D 语言尚未成熟,其编译器也在开发中,更新频繁,这对于 D 语言用于实际开发产生了不小的困扰。但是,作为一个编程爱好者,我很喜爱它的设计理念、语法,它的社区也越来越庞大。如果有一天,D 语言形成标准,必将大放异彩,祝愿它有一个美好的明天。