C 程序員如何使用 D 編程
每個有經驗的 C 程序員都積累了一系列的習慣和技術,這幾乎成了第二天性。有時候,當學習一門新語言時,這些習慣會因爲太令人舒適而使人看不到新語言中等價的方法。所以下面收集了一些常用的 C 技術,以及如何在 D 中完成同樣的任務。
因爲 C 沒有面向對象的特徵,所以有關面向對象的論述請參見 C++ 程序員如何使用 D 編程 。
C 的預處理程序在 C 的預處理程序 vs D 中討論。
- 獲得一個類型的大小
- 獲得一個類型的最大值和最小值
- 基本類型
- 特殊的浮點值
- 浮點除法中的餘數
- 在浮點比較中處理 NAN
- 斷言
- 初始化數組的所有元素
- 遍歷整個數組
- 創建可變大小數組
- 字符串連接
- 格式化打印
- 函數的前向引用
- 無參數的函數
- 帶標號的 break 和 continue
- goto 語句
- 結構標記名字空間
- 查找字符串
- 設置結構成員對齊方式
- 匿名結構和聯合
- 聲明結構類型和變量
- 獲得結構成員的偏移量
- 聯合的初始化
- 結構的初始化
- 數組的初始化
- 轉義字符串文字量
- Ascii 字符 vs 寬字符
- 同枚舉相應的數組
- 創建一個新的 typedef 類型
- 比較結構
- 比較字符串
- 給數組排序
- 訪問易失性內存
- 字符串文字量
- 遍歷數據結構
- 無符號右移
獲得一個類型的大小
C 的方式
() ( *) () ( Foo)
D 的方式
使用 sizeof 屬性:
.sizeof ( *).sizeof .sizeof Foo.sizeof
獲得一個類型的最大值和最小值
C 的方式
CHAR_MAX CHAR_MIN ULONG_MAX DBL_MIN
D 的方式
.max .min .max .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 的方式
NAN INFINITY DBL_DIG DBL_EPSILON DBL_MANT_DIG DBL_MAX_10_EXP DBL_MAX_EXP DBL_MIN_10_EXP DBL_MIN_EXP
D 的方式
.nan .infinity .dig .epsilon .mant_dig .max_10_exp .max_exp .min_10_exp .min_exp
浮點除法中的餘數
C 的方式
f = fmodf(x,y); d = fmod(x,y); e = fmodl(x,y);
D 的方式
D 支持浮點操作數的求餘(‘%’)運算符:
f = x % y; d = x % y; e = x % y;
在浮點比較中處理 NAN
C 的方式
C 對操作數爲 NAN 的比較的結果沒有定義,並且很少有 C 編譯器對此進行檢查(Digital Mars C 編譯器是個例外,DM 的編譯器檢查操作數是否是 NAN)。
(isnan(x) || isnan(y)) result = FALSE; result = (x < y);
D 的方式
D 的比較和運算符提供對 NAN 參數的完全支持。
result = (x < y);
斷言是所有防衛性編碼策略的必要組成部分
C 的方式
C 不直接支持斷言,但是它支持 __FILE__ 和 __LINE__ ,可以以它們爲基礎使用宏構建斷言。事實上,除了斷言以外,__FILE__ 和 __LINE__ 沒有什麼其他的實際用處。
assert(e == 0);
D 的方式
D 直接將斷言構建在語言裏:
(e == 0);
[註記:追蹤函數?]
初始化數組的所有元素
C 的方式
array[ARRAY_LENGTH]; (i = 0; i < ARRAY_LENGTH; i++) array[i] = value;
D 的方式
array[17]; array[] = value;
遍歷整個數組
C 的方式
數組長度另外定義,或者使用笨拙的 sizeof() 表達式。
array[ARRAY_LENGTH]; (i = 0; i < ARRAY_LENGTH; i++) func(array[i]);
或:
array[17]; (i = 0; i < (array) / (array[0]); i++) func(array[i]);
D 的方式
可以使用“length”屬性訪問數組的長度:
array[17]; (i = 0; i < array.length; i++) func(array[i]);
或者使用更好的方式:
array[17]; ( value; array) func(value);
創建可變大小數組
C 的方式
C 不能處理這種數組。需要另外創建一個變量保存長度,並顯式地管理數組大小:
array_length; *array; *newarray; newarray = ( *) realloc(array, (array_length + 1) * ()); (!newarray) error(); array = newarray; array[array_length++] = x;
D 的方式
D 支持動態數組,可以輕易地改變大小。D 支持所有的必需的內存管理。
[] array; array.length = array.length + 1; array[array.length - 1] = x;
字符串連接
C 的方式
有幾個難題需要解決,如什麼時候可以釋放內存、如何處理空指針、得到字符串的長度以及內存分配:
*s1; *s2; *s; free(s); s = ( *)malloc((s1 ? strlen(s1) : 0) + (s2 ? strlen(s2) : 0) + 1); (!s) error(); (s1) strcpy(s, s1); *s = 0; (s2) strcpy(s + strlen(s), s2); hello[] = ; *news; size_t lens = s ? strlen(s) : 0; news = ( *)realloc(s, (lens + (hello) + 1) * ()); (!news) error(); s = news; memcpy(s + lens, hello, (hello));
D 的方式
D 爲 char 和 wchar 數組分別重載了‘~’和‘~=’運算符用於連接和追加:
[] s1; [] s2; [] s; s = s1 ~ s2; s ~= ;
格式化打印
C 的方式
printf() 是通用的格式化打印例程:
printf(, ntimes);
D 的方式
我們還能說什麼呢?這裏還是 printf() 的天下:
stdio; printf(, ntimes);
(譯註:其實現在標準庫 phobos 中已經有了 write 和 writeln 這兩個函數,但是尚未定型。)
函數的前向引用
C 的方式
不能引用尚未聲明的函數。因此,如果要調用源文件中尚未出現的函數,就必須在調用之前插入函數聲明。
forwardfunc(); myfunc() { forwardfunc(); } forwardfunc() { ... }
D 的方式
程序被看作一個整體,所以沒有必要編寫前向聲明,而且這也是不允許的!D 避免了編寫前向函數聲明的繁瑣和由於重複編寫前向函數聲明而造成的錯誤。函數可以按照任何順序定義。
myfunc() { forwardfunc(); } forwardfunc() { ... }
無參數的函數
C 的方式
function();
D 的方式
D 是強類型語言,所以沒有必要顯式地說明一個函數沒有參數,只需在聲明時不寫參數即可。
function() { ... }
帶標號的 break 和 continue
C 的方式
break 和 continue 只用於嵌套中最內層的循環或 switch 結構,所以必須使用 goto 實現多層的 break:
(i = 0; i < 10; i++) { (j = 0; j < 10; j++) { (j == 3) Louter; (j == 4) L2; } L2: ; } Louter: ;
D 的方式
break 和 continue 語句後可以帶有標號。該標號是循環或 switch 結構外圍的,break 用於退出該循環。
Louter: (i = 0; i < 10; i++) { (j = 0; j < 10; j++) { (j == 3) Louter; (j == 4) Louter; } }
Goto 語句
C 的方式
飽受批評的 goto 語句是專業 C 程序員的一個重要工具。有時,這是對控制流語句的必要補充。
D 的方式
許多 C 方式的 goto 語句可以使用 D 中的標號 break 和 continue 語句替代。但是 D 對於實際的程序員來說是一門實際的語言,他們知道什麼時候應該打破規則。所以,D 當然支持 goto !
結構標記名字空間
C 的方式
每次都要將 struct 關鍵字寫在結構類型名之前簡直是煩人透頂,所以習慣的用法是:
ABC { ... } ABC;
D 的方式
結構標記名字不再位於單獨的名字空間,它們同普通的名字共享一個名字空間。因此:
ABC { ... };
查找字符串
C 的方式
給定一個字符串,將其同一系列可能的值逐個比較,如果匹配就施行某種動作。該方法的典型應用要數命令行參數處理。
dostring( *s) { Strings { Hello, Goodbye, Maybe, Max }; *table[] = { , , }; i; (i = 0; i < Max; i++) { (strcmp(s, table[i]) == 0) ; } (i) { Hello: ... Goodbye: ... Maybe: ... : ... } }
該方法的問題是需要維護三個並行的數據結構:枚舉、表和 switch-case 結構。如果有很多的值,維護這三種數據結構之間的對應關係就不那麼容易了,所以這種情形就成了孕育 bug 的溫牀。另外,如果值的數目很大,相對於簡單的線性查找,採用二叉查找或者散列表會極大地提升性能。但是它們需要更多時間進行編碼,並且調試難度也更大。典型地,人們會簡單地放棄實現這些高效而複雜的數據結構。
D 的方式
D 擴展了 switch 語句的概念,現在它能像處理數字一樣處理字符串。所以,字符串查找的實現變得直接:
dostring([] s) { (s) { : ... : ... : ... : ... } }
添加新的 case 子句也變得容易起來。可以由編譯器爲其生成一種快速的查找方案,這樣也就避免了由於手工編碼而消耗的時間及引入的 bug 。