知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070430
点击下面网址查看原文:
http://www.d-programming-language-china.org
by: uFramer D语言论坛 http://www.d-programming-language-china.org
from: http://www.digitalmars.com/d/ctod.html
version: 基于D 1.014
Et tu, D? Then fall, C! -- William Nerdspeare
每个有经验的 C 程序员都积累了一系列的习惯和技术,这几乎成了第二天性。有时候,当学习一门新语言时,这些习惯会因为太令人舒适而使人看不到新语言中等价的方法。所以下面收集了一些常用的 C 技术,以及如何在 D 中完成同样的任务。
因为 C 没有面向对象的特征,所以有关面向对象的论述请参见 C++ 程序员如何使用 D 编程 。
http://digitalmars.com/d/cpptod.html
C 的预处理程序在 C 的预处理程序 vs D 中讨论。
http://digitalmars.com/d/pretod.html
获得一个类型的大小
C 的方式
sizeof(int)
sizeof(char *)
sizeof(double)
sizeof(struct Foo)
D 的方式
使用 size 属性:
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 => real
_Imaginary long double => ireal
_Complex long double => creal
尽管 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 r = fmodl(x,y);
D 的方式
D 支持浮点操作数的求余(‘%’)运算符:
float f = x % y;
double d = x % y;
real r = x % y;
在浮点比较中处理 NAN
C 的方式
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 直接将断言构建在语言里:( 本文出处: http://www.d-programming-language-china.org )
初始化数组的所有元素
C 的方式
#define ARRAY_LENGTH 17
int array[ARRAY_LENGTH];
for (i = 0; i < ARRAY_LENGTH; i++)
array = 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);
或:
int array[17];
for (i = 0; i < sizeof(array) / sizeof(array[0]); i++)
func(array);
D 的方式
可以使用“length”属性访问数组的长度:( 本文出处: http://www.d-programming-language-china.org )
int array[17];
for (i = 0; i < array.length; i++)
func(array);
或者使用更好的方式:
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;
// Concatenate s1 and s2, and put result in 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);
// Append "hello" to 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 数组分别重载了‘~’和‘~=’运算符用于连接和追加:( 本文出处: http://www.d-programming-language-china.org )
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() 的天下:
printf("Calling all cars %d times!\n", ntimes);
printf()的改进型writefln实现了类型安全和线程安全。
import std.stdio;
writefln("Calling all cars %s times!", ntimes);
函数的前向引用
C 的方式
不能引用尚未声明的函数。因此,如果要调用源文件中尚未出现的函数,就必须在调用之前插入函数声明。
void forwardfunc();
void myfunc()
{
forwardfunc();
}
void forwardfunc()
{
...
}
D 的方式
程序被看作一个整体,所以没有必要编写前向声明,而且这也是不允许的!D 避免了编写前向函数声明的繁琐和由于重复编写前向函数声明而造成的错误。函数可以按照任何顺序定义。( 本文出处: http://www.d-programming-language-china.org )
void myfunc()
{
forwardfunc();
}
void forwardfunc()
{
...
}
无参数的函数
C 的方式
D 的方式
D 是强类型语言,所以没有必要显式地说明一个函数没有参数,只需在声明时不写参数即可。
带标号的 break 和 continue
C 的方式
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 !( 本文出处: http://www.d-programming-language-china.org )
结构标记名字空间
C 的方式
每次都要将 struct 关键字写在结构类型名之前简直是烦人透顶,所以习惯的用法是:
typedef struct ABC { ... } ABC;
D 的方式
结构标记名字不再位于单独的名字空间,它们同普通的名字共享一个名字空间。因此:
查找字符串
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) == 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 。( 本文出处: http://www.d-programming-language-china.org )
设置和结构成员对齐方式
C 的方式
这是使用命令行选项完成的,而且该效果会影响整个程序,并且如果有模块或者库没有重新编译,结果会是悲剧性的。为了解决这个问题,需要用到
#pragma pack(1)
struct ABC
{
...
};
#pragma pack()
但是,无论在理论上还是实际上,#pragma 在编译器之间都是不可移植的。
D 的方式
很显然,设置对齐的主要目的是使数据可移植,因此需要一种表述结构的可移植的方式。
struct ABC
{
int z; // z is aligned to the default
align (1) int x; // x is byte aligned
align (4)
{
... // declarations in {} are dword aligned
}
align (2): // switch to word alignment from here on
int y; // y is word aligned
}
匿名结构和联合
有时,有必要控制嵌套在结构或联合内部的结构的分布。
C 的方式
C 不允许出现匿名的结构或联合,这意味着需要使用傀儡标记名和傀儡成员:
struct Foo
{
int i;
union Bar
{
struct Abc { int x; long y; } _abc;
char *p;
} _bar;
};
#define x _bar._abc.x
#define y _bar._abc.y
#define p _bar.p
struct Foo f;
f.i;
f.x;
f.y;
f.p;
这样做不仅笨拙,由于使用了宏,还使符号调试器无法理解程序究竟做了什么,并且宏还占据了全局作用域而不是结构作用域。
D 的方式
匿名结构和联合用来以一种更自然的方式控制分布:( 本文出处: http://www.d-programming-language-china.org )
struct Foo
{
int i;
union
{
struct { int x; long y; }
char* p;
}
}
Foo f;
f.i;
f.x;
f.y;
f.p;
声明结构类型和变量
C 的方式
可以在一条以分号结尾的语句中完成:
struct Foo { int x; int y; } foo;
或者分为两条语句:
struct Foo { int x; int y; }; // note terminating ;
struct Foo foo;
D 的方式
结构的定义和声明不能在一条语句中完成:
struct Foo { int x; int y; } // 注意结尾处没有‘;’
Foo foo;
这意味着结尾的‘;’可以去掉,免得还要区分 struct {} 和函数及语句块的 {} 之间在分号用法上的不同。
获得结构成员的偏移量
C 的方式
很自然,又用了一个宏:( 本文出处: http://www.d-programming-language-china.org )
#include <stddef>
struct Foo { int x; int y; };
off = offsetof(Foo, y);
D 的方式
偏移量只是另一个属性:
struct Foo { int x; int y; }
off = Foo.y.offsetof;
联合的初始化
C 的方式
联合的初始化采用“首个成员”规则:
union U { int a; long b; };
union U x = { 5 }; // initialize member 'a' to 5
为联合添加成员或者重新排列成员的结果对任何的初始化语句来说都是灾难性的。
D 的方式
在 D 中,初始化那个成员是显式地指定的:
union U { int a; long b; }
U x = { a:5 };
还避免了误解和维护问题。( 本文出处: http://www.d-programming-language-china.org )
结构的初始化
C 的方式
成员按照它们在 {} 内的顺序初始化:
struct S { int a; int b; };
struct S x = { 5, 3 };
对于小结构来说,这不是什么问题,但当成员的个数变得很大时,小心地排列初始值以同声明它们的顺序对应变得很繁琐。而且,如果新加了或者重新排列了成员的话,所有的初始化语句都需要进行适当地修改。这可是 bug 的雷区。
D 的方式
可以显式地初始化成员:
struct S { int a; int b; }
S x = { b:3, a:5 }
这样意义明确,并且不依赖于位置。
数组的初始化
C 的方式
C 初始化数组时依赖于位置:
嵌套的数组可以使用 {} ,也可以不使用 {}:( 本文出处: http://www.d-programming-language-china.org )
int b[3][2] = { 2,3, {6,5}, 3,4 };
D 的方式
D 也依赖于位置,但是还可以使用索引,下面的语句都产生同样的结果:
int[3] a = [ 3, 2, 0 ];
int[3] a = [ 3, 2 ]; // 未提供的初始值被视为 0 ,如同 C 一样
int[3] a = [ 2:0, 0:3, 1:2 ];
int[3] a = [ 2:0, 0:3, 2 ]; // 如果未提供,索引为前面的索引加一
如果数组的下标为枚举的话,这会很方便。而且枚举的顺序可以变更,也可以加入新的枚举值:
enum color { black, red, green }
int[3] c = [ black:3, green:2, red:5 ];
必须显式地初始化嵌套数组:
int[2][3] b = [ [2,3], [6,5], [3,4] ];
int[2][3] b = [[2,6,3],[3,5,4]]; // 错误
转义字符串文字量
C 的方式
C 在 DOS 文件系统中会遇到问题,因为字符串中的‘\’是转义符。如果要使用文件 c:\root\file.c :
char file[] = "c:\\root\\file.c";
如果使用正则表达式的话,会让人很难高兴起来。考虑匹配引号字符串的转义序列:( 本文出处: http://www.d-programming-language-china.org )
在 C 中,令人恐怖的表示如下:
char quoteString[] = "\"[^\\\\]*(\\\\.[^\\\\]*)*\"";
D 的方式
字符串本身是 WYSIWYG(所见即所得)的。转义字符位于另外的字符串中。所以:
char[] file = r"c:\root\file.c";
char[] quoteString = \" r"[^\\]*(\\.[^\\]*)*" \";
著名的 hello world 字符串变为:
char[] hello = "hello world" \n;
Ascii 字符 vs 宽字符
现代的程序设计工作需要语言以一种简单的方法支持 wchar 字符串,这样你的程序就可以实现国际化。
C 的方式
C 使用 wchar_t 并在字符串前添加 L 前缀:( 本文出处: http://www.d-programming-language-china.org )
#include <wchar.h>
char foo_ascii[] = "hello";
wchar_t foo_wchar[] = L"hello";
如果代码需要同时兼容 ascii 和 wchar 的化,情况会变得更糟。需要使用宏来屏蔽 ascii 和 wchar 字符串的差别:
#include <tchar.h>
tchar string[] = TEXT("hello");
D 的方式
字符串的类型由语义分析决定,所以没有必要用宏调用将字符串包裹起来:
char[] foo_ascii = "hello"; // 字符串使用 ascii
wchar[] foo_wchar = "hello"; // 字符串使用 wchar
同枚举相应的数组
C 的方式
考虑:
enum COLORS { red, blue, green, max };
char *cstring[max] = {"red", "blue", "green" };
当项的数目较小时,很容易保证其正确。但是如果数目很大,当加入新的项时就会很难保证其正确性。
D 的方式
enum COLORS { red, blue, green }
char[][COLORS.max + 1] cstring =
[
COLORS.red : "red",
COLORS.blue : "blue",
COLORS.green : "green",
];
创建一个新的 typedef 类型
C 的方式
C 中的 typedef 是弱的,也就是说,他们并不真正引入一个类型。编译器并不区分 typedef 类型和它底层的类型。( 本文出处: http://www.d-programming-language-china.org )
typedef void *Handle;
void foo(void *);
void bar(Handle);
Handle h;
foo(h); // 未捕获的编码错误
bar(h); // ok
C 的解决方案是创建一个傀儡结构,目的是获得新类型才有的类型检查和重载能力。(译注:这里捎带的涉及了 C++ 中的重载问题)
struct Handle__ { void *value; }
typedef struct Handle__ *Handle;
void foo(void *);
void bar(Handle);
Handle h;
foo(h); // 语法错误
bar(h); // ok
如果要给这个类型定一个默认值,需要定义一个宏,一个命名规范,然后时刻遵守这个规范:
#define HANDLE_INIT ((Handle)-1)
Handle h = HANDLE_INIT;
h = func();
if (h != HANDLE_INIT)
...
对于采用结构的那种解决方案,事情甚至变得更复杂:
struct Handle__ HANDLE_INIT;
void init_handle() // 在开始处调用这个函数
{
HANDLE_INIT.value = (void *)-1;
}
Handle h = HANDLE_INIT;
h = func();
if (memcmp(&h,&HANDLE_INIT,sizeof(Handle)) != 0)
...
需要记住四个名字:Handle、HANDLE_INIT、struct Handle__ 和 value 。
D 的方式
不需要上面那样的习惯构造。只需要写:
typedef void* Handle;
void foo(void*);
void bar(Handle);
Handle h;
foo(h);
bar(h);
为了处理默认值,可以给 typedef 添加一个初始值,可以使用 .init 属性访问这个初始值:( 本文出处: http://www.d-programming-language-china.org )
typedef void* Handle = cast(void*)(-1);
Handle h;
h = func();
if (h != Handle.init)
...
只需要记住一个名字:Handle 。
结构比较
C 的方式
尽管 C 为结构赋值定义了一种简单、便捷的方法:
struct A x, y;
...
x = y;
却不支持结构之间的比较。因此,如果要比较两个结构实例之间的相等性:
#include <string.h>
struct A x, y;
...
if (memcmp(&x, &y, sizeof(struct A)) == 0)
...
请注意这种方法的笨拙,而且在类型检查上得不到语言的任何支持。
memcmp() 中有一个潜伏的 bug 。结构的分布中,由于对齐的原因,可能会有“空洞”。C 不保证这些空洞中为何值,所以两个不同的结构实例可能拥有所有成员的值都对应相等,但比较的结果却由于空洞中的垃圾的存在而为“不等”。
D 的方式
D 的方式直接而显然:( 本文出处: http://www.d-programming-language-china.org )
A x, y;
...
if (x == y)
...
比较字符串
C 的方式
库函数 strcmp() 用于这个目的:
char string[] = "hello";
if (strcmp(string, "betty") == 0) // 字符串匹配吗?
...
C 的字符串以‘0’结尾,所以由于需要不停地检测结尾的‘0’,C 的方式在效率上先天不足。
D 的方式
为什么不用 == 运算符呢?
char[] string = "hello";
if (string == "betty")
...
D 的字符串另外保存有长度。因此,字符串比较的实现可以比 C 的版本快得多(它们之间的差异就如同 C 的 memcmp() 同 strcmp() 之间的差异一样)。
D 还支持字符串的比较运算符:
char[] string = "hello";
if (string < "betty")
...
这对于排序/查找是很有用的。
数组的排序
C 的方式
尽管许多的 C 程序员不厌其烦地一遍一遍实现着冒泡排序,C 中正确的方法却是使用 qsort() :( 本文出处: http://www.d-programming-language-china.org )
int compare(const void *p1, const void *p2)
{
type *t1 = (type *)p1;
type *t1 = (type *)p2;
return *t1 - *t2;
}
type array[10];
...
qsort(array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare);
必须为每种类型编写一个 compare() 函数,而这些工作极易出错。
D 的方式
这恐怕是最容易的排序方式了:
type[] array;
...
array.sort; // 适当地为数组排序
访问易失性内存
C 的方式
如果要访问易失性内存,如共享内存或者内存映射 I/O ,需要一个易失性的指针:
volatile int *p = address;
i = *p;
D 的方式
D 有一种易失性语句,而不是一种类型修饰符:
int* p = address;
volatile { i = *p; }
字符串文字量
C 的方式
C 的字符串文字量不能跨越多行,所以需要用‘\’将文本块分割为多行:
"This text spans\n\
multiple\n\
lines\n"
如果有很多的文本的话,这种做法是很繁琐的。
D 的方式
字符串文字量可以跨越多行,如下所示:( 本文出处: http://www.d-programming-language-china.org )
"This text spans
multiple
lines
"
所以可以简单用剪切/粘贴将成块的文字插入到 D 源码中。
遍历数据结构
C 的方式
考虑一个遍历递归数据结构的函数。在这个例子中,有一个简单的字符串符号表。数据结构为一个二叉树数组。代码需要穷举这个结构以找到其中的特定的字符串,并检测它是否是唯一的实例。
为了完成这项工作,需要一个辅助函数 membersearchx 递归地遍历整棵树。该辅助函数需要读写树外部的一些上下文,所以创建了一个 struct Paramblock ,用指针指向它的以提高效率。
struct Symbol
{
char *id;
struct Symbol *left;
struct Symbol *right;
};
struct Paramblock
{
char *id;
struct Symbol *sm;
};
static void membersearchx(struct Paramblock *p, struct Symbol *s)
{
while (s)
{
if (strcmp(p->id,s->id) == 0)
{
if (p->sm)
error("ambiguous member %s\n",p->id);
p->sm = s;
}
if (s->left)
membersearchx(p,s->left);
s = s->right;
}
}
struct Symbol *symbol_membersearch(Symbol *table[], int tablemax, char *id)
{
struct Paramblock pb;
int i;
pb.id = id;
pb.sm = NULL;
for (i = 0; i < tablemax; i++)
{
membersearchx(pb, table);
}
return pb.sm;
}
D 的方式
这是同一个算法的 D 版本,代码量大大少于上一个版本。因为嵌套函数可以访问外围函数的变量,所以就不需要 Paramblock 或者处理它的簿记工作的细节了。嵌套的辅助函数完全处于使用它的函数的内部,提高了局部性和可维护性。
这两个版本的性能没什么差别。
class Symbol
{ char[] id;
Symbol left;
Symbol right;
}
Symbol symbol_membersearch(Symbol[] table, char[] id)
{ Symbol sm;
void membersearchx(Symbol s)
{
while (s)
{
if (id == s.id)
{
if (sm)
error("ambiguous member %s\n", id);
sm = s;
}
if (s.left)
membersearchx(s.left);
s = s.right;
}
}
for (int i = 0; i < table.length; i++)
{
membersearchx(table);
}
return sm;
}
无符号右移
C 的方式
如果左操作数是有符号整数类型,右移运算符>> 和>>= 表示有符号右移;如果左操作数是无符号整数类型,右移运算符 >> 和>>= 表示无符号右移。如果要对 int 施行无符号右移,必须使用类型转换:
int i, j;
...
j = (unsigned)i>> 3;
如果 i 是 int ,这种方法会工作得很好。但是如果 i 是一个 typedef 类型,( 本文出处: http://www.d-programming-language-china.org )
myint i, j;
...
j = (unsigned)i >> 3;
并且 myint 恰巧是 long int ,则这个类型转换会悄无声息地丢掉最重要的那些 bit ,给出一个不正确的结果。
D 的方式
D 的右移运算符>> 和>>= 的行为同它们在 C 中的行为相同。但是D 还支持显式右移运算符>>> 和>>>= ,无论左操作数是否有符号,都会执行无符号右移。因此,
myint i, j;
...
j = i >>> 3;
避免了不安全的类型转换并且对于任何整数类型都能如你所愿的工作。
Dynamic Closures动态闭包
The C Way
Consider a reusable container type. In order to be reusable, it must support a way to apply arbitrary code to each element of the container. This is done by creating an apply function that accepts a function pointer to which is passed each element of the container contents.
A generic context pointer is also needed, represented here by void *p. The example here is of a trivial container class that holds an array of ints, and a user of that container that computes the maximum of those ints.
struct Collection
{
int array[10];
void apply(void *p, void (*fp)(void *, int))
{
for (int i = 0; i < sizeof(array)/sizeof(array[0]); i++)
fp(p, array);
}
};
void comp_max(void *p, int i)
{
int *pmax = (int *)p;
if (i > *pmax)
*pmax = i;
}
void func(Collection *c)
{
int max = INT_MIN;
c->apply(&max, comp_max);
}
The C way makes heavy use of pointers and casting. The casting is tedious, error prone, and loses all type safety.
The D Way
The D version makes use of delegates to transmit context information for the apply function, and nested functions both to capture context information and to improve locality.( 本文出处: http://www.d-programming-language-china.org )
class Collection
{
int[10] array;
void apply(void delegate(int) fp)
{
for (int i = 0; i < array.length; i++)
fp(array);
}
}
void func(Collection c)
{
int max = int.min;
void comp_max(int i)
{
if (i > max)
max = i;
}
c.apply(comp_max);
}
Pointers are eliminated, as well as casting and generic pointers. The D version is fully type safe. An alternate method in D makes use of function literals:
void func(Collection c)
{
int max = int.min;
c.apply(delegate(int i) { if (i > max) max = i; } );
}
eliminating the need to create irrelevant function names.
Variadic Function Parameters
The task is to write a function that takes a varying number of arguments, such as a function that sums its arguments.
The C Way
#include <stdio.h>
#include <stdarg.h>
int sum(int dim, ...)
{ int i;
int s = 0;
va_list ap;
va_start(ap, dim);
for (i = 0; i < dim; i++)
s += va_arg(ap, int);
va_end(ap);
return s;
}
int main()
{
int i;
i = sum(3, 8,7,6);
printf("sum = %d\n", i);
return 0;
}
There are two problems with this. The first is that the sum function needs to know how many arguments were supplied. It has to be explicitly written, and it can get out of sync with respect to the actual number of arguments written. The second is that there's no way to check that the types of the arguments provided really were ints, and not doubles, strings, structs, etc.
The D Way
The ... following an array parameter declaration means that the trailing arguments are collected together to form an array. The arguments are type checked against the array type, and the number of arguments becomes a property of the array:( 本文出处: http://www.d-programming-language-china.org )
int sum(int[] values ...)
{
int s = 0;
foreach (int x; values)
s += x;
return s;
}
int main()
{
int i;
i = sum(8,7,6);
printf("sum = %d\n", i);
return 0;
}
( lastupdate:20070430 最新文章请访问http://www.d-programming-language-china.org )关于一大步成功社区:
yidabu提倡在交流中学习,在分享中提高
收集感兴趣的知识,写下心得,通过网络与别人一起分享
理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的
网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台