| C 程序员如何使用 D 编程 每个有经验的 C 程序员都积累了一系列的习惯和技术,这几乎成了第二天性。有时候,当学习一门新语言时,这些习惯会因为太令人舒适而使人看不到新语言中等价的方法。所以下面收集了一些常用的 C 技术,以及如何在 D 中完成同样的任务。
 因为 C 没有面向对象的特征,所以有关面向对象的论述请参见 C++ 程序员如何使用 D 编程 。 
 C 的预处理程序在 C 的预处理程序 vs D 中讨论。 
 
获得一个类型的大小 
获得一个类型的最大值和最小值 
基本类型 
特殊的浮点值 
浮点除法中的余数 
在浮点比较中处理 NAN 
断言 
初始化数组的所有元素 
遍历整个数组 
创建可变大小数组 
字符串连接 
格式化打印 
函数的前向引用 
无参数的函数 
带标号的 break 和 continue 
goto 语句 
结构标记名字空间 
查找字符串 
设置结构成员对齐方式 
匿名结构和联合 
声明结构类型和变量 
获得结构成员的偏移量 
联合的初始化 
结构的初始化 
数组的初始化 
转义字符串文字量 
Ascii 字符 vs 宽字符 
同枚举相应的数组 
创建一个新的 typedef 类型 
比较结构 
比较字符串 
给数组排序 
访问易失性内存 
字符串文字量 
遍历数据结构 
无符号右移 
 获得一个类型的大小C 的方式	sizeof(int)
	sizeof(char *)
	sizeof(double)
	sizeof(struct Foo)
 D 的方式使用 sizeof 属性:         int.sizeof
        (char *).sizeof
        double.sizeof
        Foo.sizeof
 获得一个类型的最大值和最小值C 的方式	#include <limits.h>
	#include <math.h>
	CHAR_MAX
	CHAR_MIN
	ULONG_MAX
	DBL_MIN
 D 的方式        char.max
        char.min
        ulong.max
        double.min
 基本类型D 中与 C 类型对应的类型        bool               =>        bit 
        char               =>        char 
        signed char        =>        byte 
        unsigned char      =>        ubyte 
        short              =>        short 
        unsigned short     =>        ushort 
        wchar_t            =>        wchar 
        int                =>        int 
        unsigned           =>        uint 
        long               =>        int 
        unsigned long      =>        uint 
        long long          =>        long 
        unsigned long long =>        ulong 
        float              =>        float 
        double             =>        double 
        long double        =>        extended 
        _Imaginary long double =>    imaginary
        _Complex long double   =>    complex
尽管 char 是一个无符号 8 bit 的类型,而 wchar 是一个无符号 16 bit 的类型,它们还是被划为独立的类型以支持重载解析和类型安全。 
 在 C 中各种整数类型和无符号类型的大小不是固定的(不同的实现的值可以不同);在 D 中它们的大小都是固定的。 
 
 特殊的浮点值C 的方式       #include <fp.h> 
       NAN 
       INFINITY 
       #include <float.h> 
       DBL_DIG 
       DBL_EPSILON 
       DBL_MANT_DIG 
       DBL_MAX_10_EXP 
       DBL_MAX_EXP 
       DBL_MIN_10_EXP 
       DBL_MIN_EXP 
D 的方式       double.nan 
       double.infinity 
       double.dig 
       double.epsilon 
       double.mant_dig 
       double.max_10_exp 
       double.max_exp 
       double.min_10_exp 
       double.min_exp 
 浮点除法中的余数C 的方式       #include <math.h> 
       float f = fmodf(x,y); 
       double d = fmod(x,y); 
       long double e = fmodl(x,y); 
D 的方式D 支持浮点操作数的求余(‘%’)运算符:        float f = x % y; 
       double d = x % y; 
       extended e = x % y; 
 在浮点比较中处理 NANC 的方式C 对操作数为 NAN 的比较的结果没有定义,并且很少有 C 编译器对此进行检查(Digital Mars C 编译器是个例外,DM 的编译器检查操作数是否是 NAN)。        #include <math.h> 
       if (isnan(x) || isnan(y)) 
	   result = FALSE; 
       else 
	   result = (x < y); 
D 的方式D 的比较和运算符提供对 NAN 参数的完全支持。        result = (x < y);        // 如果 x 或 y 为 nan ,值为 false
 
 断言是所有防卫性编码策略的必要组成部分C 的方式C 不直接支持断言,但是它支持 __FILE__ 和 __LINE__ ,可以以它们为基础使用宏构建断言。事实上,除了断言以外,__FILE__ 和 __LINE__ 没有什么其他的实际用处。         #include <assert.h> 
       assert(e == 0); 
D 的方式D 直接将断言构建在语言里:        assert(e == 0); 
 [注记:追踪函数?] 
 
 初始化数组的所有元素C 的方式       #define ARRAY_LENGTH        17 
       int array[ARRAY_LENGTH]; 
       for (i = 0; i < ARRAY_LENGTH; i++) 
	   array[i] = value; 
D 的方式       int array[17]; 
       array[] = value; 
 遍历整个数组C 的方式数组长度另外定义,或者使用笨拙的 sizeof() 表达式。        #define ARRAY_LENGTH        17 
       int array[ARRAY_LENGTH]; 
       for (i = 0; i < ARRAY_LENGTH; i++) 
	   func(array[i]); 
或:         int array[17]; 
       for (i = 0; i < sizeof(array) / sizeof(array[0]); i++) 
	   func(array[i]); 
D 的方式可以使用“length”属性访问数组的长度:        int array[17]; 
       for (i = 0; i < array.length; i++) 
	   func(array[i]); 
或者使用更好的方式:        int array[17]; 
       foreach (int value; array)
	   func(value); 
 创建可变大小数组C 的方式C 不能处理这种数组。需要另外创建一个变量保存长度,并显式地管理数组大小:                 #include <stdlib.h> 
               int array_length; 
               int *array; 
               int *newarray; 
               newarray = (int *) realloc(array, (array_length + 1) * sizeof(int)); 
               if (!newarray) 
                   error("out of memory"); 
               array = newarray; 
               array[array_length++] = x; 
D 的方式D 支持动态数组,可以轻易地改变大小。D 支持所有的必需的内存管理。                int[] array; 
               array.length = array.length + 1;
               array[array.length - 1] = x; 
 字符串连接C 的方式有几个难题需要解决,如什么时候可以释放内存、如何处理空指针、得到字符串的长度以及内存分配:   
               #include <string.h> 
               char *s1; 
               char *s2; 
               char *s; 
               // 连接 s1 和 s2,并将结果存入 s 
               free(s); 
               s = (char *)malloc((s1 ? strlen(s1) : 0) + 
                                  (s2 ? strlen(s2) : 0) + 1); 
               if (!s) 
                   error("out of memory"); 
               if (s1) 
                   strcpy(s, s1); 
               else 
                   *s = 0; 
               if (s2) 
                   strcpy(s + strlen(s), s2); 
               // 追加 "hello" 到 s 尾部
               char hello[] = "hello"; 
               char *news; 
               size_t lens = s ? strlen(s) : 0; 
               news = (char *)realloc(s, (lens + sizeof(hello) + 1) * sizeof(char)); 
               if (!news) 
                   error("out of memory"); 
               s = news; 
               memcpy(s + lens, hello, sizeof(hello)); 
D 的方式D 为 char 和 wchar 数组分别重载了‘~’和‘~=’运算符用于连接和追加:                 char[] s1; 
               char[] s2; 
               char[] s; 
               s = s1 ~ s2; 
               s ~= "hello"; 
 格式化打印C 的方式printf() 是通用的格式化打印例程:                #include <stdio.h> 
               printf("Calling all cars %d times!\n", ntimes); 
D 的方式我们还能说什么呢?这里还是 printf() 的天下:                import stdio; 
               printf("Calling all cars %d times!\n", ntimes); (译注:其实现在标准库 phobos 中已经有了 write 和 writeln 这两个函数,但是尚未定型。)  
 函数的前向引用C 的方式不能引用尚未声明的函数。因此,如果要调用源文件中尚未出现的函数,就必须在调用之前插入函数声明。                void forwardfunc(); 
               void myfunc() 
               { 
                   forwardfunc(); 
               } 
               void forwardfunc() 
               { 
                   ... 
               } 
D 的方式程序被看作一个整体,所以没有必要编写前向声明,而且这也是不允许的!D 避免了编写前向函数声明的繁琐和由于重复编写前向函数声明而造成的错误。函数可以按照任何顺序定义。                void myfunc() 
               { 
                   forwardfunc(); 
               } 
               void forwardfunc() 
               { 
                   ... 
               } 
 无参数的函数C 的方式               void function(void); 
 D 的方式D 是强类型语言,所以没有必要显式地说明一个函数没有参数,只需在声明时不写参数即可。                void function() 
               { 
                   ... 
               } 
 带标号的 break 和 continueC 的方式break 和 continue 只用于嵌套中最内层的循环或 switch 结构,所以必须使用 goto 实现多层的 break:                for (i = 0; i < 10; i++) 
               { 
                   for (j = 0; j < 10; j++) 
                   { 
                       if (j == 3) 
                           goto Louter; 
                       if (j == 4) 
                           goto L2; 
                   } 
                 L2: 
                   ; 
               } 
           Louter: 
               ; 
D 的方式break 和 continue 语句后可以带有标号。该标号是循环或 switch 结构外围的,break 用于退出该循环。              Louter: 
               for (i = 0; i < 10; i++) 
               { 
                   for (j = 0; j < 10; j++) 
                   { 
                       if (j == 3) 
                           break Louter; 
                       if (j == 4) 
                           continue Louter; 
                   } 
               } 
               // break Louter 跳转到这里
 Goto 语句C 的方式饱受批评的 goto 语句是专业 C 程序员的一个重要工具。有时,这是对控制流语句的必要补充。  D 的方式许多 C 方式的 goto 语句可以使用 D 中的标号 break 和 continue 语句替代。但是 D 对于实际的程序员来说是一门实际的语言,他们知道什么时候应该打破规则。所以,D 当然支持 goto !  
 结构标记名字空间C 的方式每次都要将 struct 关键字写在结构类型名之前简直是烦人透顶,所以习惯的用法是:                 typedef struct ABC { ... } ABC; 
D 的方式结构标记名字不再位于单独的名字空间,它们同普通的名字共享一个名字空间。因此:                 struct ABC { ... }; 
 查找字符串C 的方式给定一个字符串,将其同一系列可能的值逐个比较,如果匹配就施行某种动作。该方法的典型应用要数命令行参数处理。                 #include <string.h> 
               void dostring(char *s) 
               { 
                   enum Strings { Hello, Goodbye, Maybe, Max }; 
                   static char *table[] = { "hello", "goodbye", "maybe" }; 
                   int i; 
                   for (i = 0; i < Max; i++) 
                   { 
                       if (strcmp(s, table[i]) == 0) 
                           break; 
                   } 
                   switch (i) 
                   { 
                       case Hello:        ... 
                       case Goodbye:        ... 
                       case Maybe:        ... 
                       default:        ... 
                   } 
               } 
该方法的问题是需要维护三个并行的数据结构:枚举、表和 switch-case 结构。如果有很多的值,维护这三种数据结构之间的对应关系就不那么容易了,所以这种情形就成了孕育 bug 的温床。另外,如果值的数目很大,相对于简单的线性查找,采用二叉查找或者散列表会极大地提升性能。但是它们需要更多时间进行编码,并且调试难度也更大。典型地,人们会简单地放弃实现这些高效而复杂的数据结构。  D 的方式D 扩展了 switch 语句的概念,现在它能像处理数字一样处理字符串。所以,字符串查找的实现变得直接:                 void dostring(char[] s) 
               { 
                   switch (s) 
                   { 
                       case "hello":        ... 
                       case "goodbye":        ... 
                       case "maybe":        ... 
                       default:        ... 
                   } 
               } 
添加新的 case 子句也变得容易起来。可以由编译器为其生成一种快速的查找方案,这样也就避免了由于手工编码而消耗的时间及引入的 bug 。  
 
 |