譯者:uframer
什么是 D 語言?
D 是一種通用的系統(tǒng)和應(yīng)用編程語言。它是比 C++ 更高級的語言,同時(shí)還保持了生成高效代碼以及直接訪問操作系統(tǒng)API和硬件的能力。D
很適合于編寫從中等規(guī)模到那些由團(tuán)隊(duì)合作完成、數(shù)百萬行代碼規(guī)模的各種程序。D 易于學(xué)習(xí),為編程者提供了很多便利,并且適用各種野心勃勃的編譯器優(yōu)化技術(shù)。
D
不是腳本語言,也不是一種解釋型語言。它不需要虛擬機(jī)、宗教、或者高于一切的哲學(xué)。它是給實(shí)際的編程者使用的實(shí)際的語言,它幫助編程者快速、可靠的完成易于維護(hù)、易于理解的代碼。
D 是數(shù)十年來實(shí)現(xiàn)多種語言編譯器的經(jīng)驗(yàn)的積累,是用那些語言構(gòu)造大型工程的嘗試的積累。D 從那些語言(主要是 C++ )那里獲得了靈感,并將
使用經(jīng)驗(yàn)和現(xiàn)實(shí)世界中的實(shí)用性來馴服它。
為什么是 D ?
確實(shí),為什么?有誰需要另一種編程語言?
自從 C 語言被發(fā)明以來,軟件工業(yè)走過了一段很長的路。許多新的概念被加入了 C++ 中,但同時(shí)維護(hù)了同 C
的向后兼容性,包括兼容了原始設(shè)計(jì)中的所有的弱點(diǎn)。有很多修正這些弱點(diǎn)的嘗試,但是兼容性是最大的困擾。同時(shí),C 和 C++
都在不斷引入新的特性。這些新特性必須被小心的加入到現(xiàn)有的結(jié)構(gòu)中,以免重寫舊的代碼。最終的結(jié)果十分復(fù)雜—— C 標(biāo)準(zhǔn)將近 500 頁,C++ 標(biāo)準(zhǔn)大概有 750
頁!C++ 實(shí)現(xiàn)起來既困難又代價(jià)高昂,造成的結(jié)果就是各種實(shí)現(xiàn)之間都有差別,因此很難寫出完全可以移植的 C++ 代碼。
C++
程序員傾向于使用語言中的孤島來編程,也就是說,他們傾向于十分精通語言中的某個(gè)特性而避免使用其他特性。盡管代碼通常在編譯器之間是可移植的,但在程序員之間移植就不那么容易了。C++
的一個(gè)長處是它支持很多根本上不同的編程風(fēng)格——但從長遠(yuǎn)來看,互相重復(fù)和互相沖突的風(fēng)格會給開發(fā)帶來妨礙。
C++ 在標(biāo)準(zhǔn)庫而不是語言核心中實(shí)現(xiàn)了可改變大小的數(shù)組和字符串拼接等。不在語言核心中實(shí)現(xiàn)這些功能造成了幾種不太理想的結(jié)果。
是否能把 C++ 的能力釋放、重新設(shè)計(jì)并重鑄到一門簡單、正交并實(shí)用的語言中呢? 這種語言是否能做到易于正確實(shí)現(xiàn),并使編譯器有能力有效地生成高度優(yōu)化的代碼呢?
現(xiàn)代編譯器技術(shù)已經(jīng)取得了很大的進(jìn)步,有些原來用作原始編譯技術(shù)的補(bǔ)充的語言特性已經(jīng)可以被忽略了(一個(gè)這樣的例子是 C
語言中的‘register’關(guān)鍵字,一個(gè)更為微妙的例子是 C
中的宏預(yù)處理程序)。我們可以依賴現(xiàn)代編譯器的優(yōu)化技術(shù)而不是使用語言特性(如同原始的編譯器所做的那樣)來獲得可以接受的代碼質(zhì)量。
D的主要目標(biāo)
* 通過加入已經(jīng)被證明的能夠提高生產(chǎn)力的特性、調(diào)整語言特性以避免常見但耗費(fèi)精力的bug的出現(xiàn),至少減少軟件開發(fā)成本10%。
* 是代碼易于在編譯器之間、在機(jī)器之間、在操作系統(tǒng)之間移植。
* 支持多種編程范式,也就是至少支持命令式、結(jié)構(gòu)化、面向?qū)ο蠛头缎途幊谭妒健?br>
* 對于熟悉 C 或者 C++ 的人來說,學(xué)習(xí)曲線要短。
* 提供必要的低級訪問能力。
* 要使 D 的編譯器從根本上易于實(shí)現(xiàn)(相對于 C++ 來說)。
* 要同本機(jī)的 C 語言應(yīng)用程序二進(jìn)制接口相兼容。
* 語法要做到上下文無關(guān)。
* 對編寫國際化的應(yīng)用程序提供便利的支持。
* 同時(shí)支持契約式編程和單元測試方法論。
* 能夠構(gòu)建輕量級的、獨(dú)立的程序。
從C/C++保留而來的特征
粗看上去 D 就像 C 和 C++ 。這樣一來學(xué)習(xí)以及將代碼移植到 D 就很容易。從 C/C++ 轉(zhuǎn)向 D 應(yīng)該很自然。程序員不必從頭學(xué)起。
使用 D 并不意味著程序員會如 Java 或者 Smalltalk 那樣被嚴(yán)格的限制在某一個(gè)運(yùn)行時(shí) vm (虛擬機(jī))上。D
沒有虛擬機(jī),編譯器直接生成可連接的目標(biāo)文件。D 如同 C 那樣被直接連接到操作系統(tǒng)。通常那些你熟悉的工具如 make 同樣適用于 D 的開發(fā)。
* D 將很大程度上保留 C/C++ 的 觀感 。它將使用相同的代數(shù)語法,絕大多數(shù)的相同表達(dá)式和語句形式,以及總體的結(jié)構(gòu)。
* D 程序既可以采用 C 風(fēng)格的 函數(shù)和數(shù)據(jù) 范式,也可以采用 C++ 風(fēng)格的 面向?qū)ο?范式,或者它們兩者的混合。
* 編譯/鏈接/調(diào)試 的開發(fā)模型將會被繼承下來,但是把 D 編譯成為字節(jié)碼然后解釋執(zhí)行也不會有任何問題。
* 異常處理 越來越多的使用經(jīng)驗(yàn)顯示,異常處理是比 C 傳統(tǒng)的“出錯代碼/全局errno變量”模型更為高級的錯誤處理模型。
* 運(yùn)行時(shí)類型識別 C++ 部分地實(shí)現(xiàn)了這個(gè)功能,而 D
更進(jìn)一步。對運(yùn)行時(shí)類型識別的完全支持將使垃圾收集運(yùn)行的更好,會使調(diào)試器的功能更強(qiáng),會使對自動持久化的支持更好等等。
* D 維持了同 C 調(diào)用慣例 的兼容。這樣就能夠使 D 程序直接訪問操作系統(tǒng)的 API 。程序員有關(guān)現(xiàn)有 API 和編程范例的知識和經(jīng)驗(yàn)可以繼續(xù)在使用 D
時(shí)使用而只需付出很少的努力。
* 運(yùn)算符重載 D 支持對運(yùn)算符的重載,這樣就可以用用戶定義的類型擴(kuò)展由基本類型構(gòu)成的類型系統(tǒng)。
* 模板 模板是實(shí)現(xiàn)范型編程的一種手段。其他的手段包括使用宏或者采用協(xié)變數(shù)據(jù)類型。使用宏已經(jīng)過時(shí)了。協(xié)變類型很直接,但是低效且缺少類型檢查。C++
模板的問題是它們太復(fù)雜,同語言的語法不和諧,還有各種各樣的類型轉(zhuǎn)換和重載規(guī)則,等等。D 提供了一種簡單得多的使用模板的方法。
* RAII(資源獲得即初始化) RAII 技術(shù)是編寫可靠軟件的重要方法之一。
* Down and dirty 編程 D 將保留 down-and-dirty
編程的能力,而不用采用別的語言編寫的外部模塊。在進(jìn)行系統(tǒng)編程時(shí),有時(shí)需要將一種指針轉(zhuǎn)換成另一種指針,或者使用匯編語言。D 的目標(biāo)不是避免 down and
dirty 編程,而是減少在進(jìn)行普通程序設(shè)計(jì)時(shí)對它們的需要。
廢棄的特征
* 對 C 的源碼級兼容性。保留對 C 的源碼級兼容的擴(kuò)展已經(jīng)有了(C++ 和
Objective-C)。在這方面的進(jìn)一步工作受制于大量的遺留代碼,已經(jīng)很難對這些代碼進(jìn)行什么重大的改進(jìn)了。
* 對 C++ 的鏈接兼容性。C++ 的運(yùn)行時(shí)對象模型太復(fù)雜了——如果要較好的支持它,基本上就是要求 D 編譯器變成一個(gè)完整的 C++ 編譯器了。
* C 預(yù)處理程序。宏處理是一種擴(kuò)展語言的簡單方法,它可以給語言加入某些語言本不支持的(對于符號調(diào)試器不可見的)特征。條件編譯、使用 #include
分層的文本、宏、符號連接等,本質(zhì)上構(gòu)成了兩種難以區(qū)分兩種語言的融合體,而不是一種語言。更糟的是(或許是最好的),C
預(yù)處理程序是一種十分原始的宏語言。是停下來的時(shí)候了,看看預(yù)處理程序是用來做什么的,并將這些功能直接設(shè)計(jì)到語言內(nèi)部。
* 多重繼承。它是一種擁有飽受爭議的價(jià)值的復(fù)雜特征。它很難用一種高效的方式實(shí)現(xiàn),而且在編譯器實(shí)現(xiàn)它時(shí)很容易出現(xiàn)各種 bug 。幾乎所有的 MI
的功能都能夠通過使用單根繼承加接口和聚集的方式實(shí)現(xiàn)。而那些只有 MI 才能支持的功能并不能彌補(bǔ)它帶來的副作用。
* 名字空間。當(dāng)鏈接獨(dú)立開發(fā)的代碼時(shí),可能會發(fā)生名字的沖突,名字空間就是解決這個(gè)問題的一種嘗試。模塊的概念更簡單并且工作得更好。
* 標(biāo)記名字空間。這是 C 的一個(gè)糟糕的特征,結(jié)構(gòu)的標(biāo)記名稱位于一個(gè)同其它符號不同的符號表中。C++ 試圖合并標(biāo)記名字空間和正常的名字空間,但同時(shí)還要維持對遺留
C 代碼的向后兼容性。造成的結(jié)果是不可打印。
* 前 向聲明。C 編譯器在語義上只知道什么東西實(shí)在詞法上位于當(dāng)前狀態(tài)之前的。C++ 進(jìn)行了一點(diǎn)點(diǎn)擴(kuò)展,類中的成員可以依賴于它之后聲明的類成員。D
更進(jìn)一步,得到了一個(gè)合情合理的結(jié)論,前向聲明根本就沒有存在的必要。函數(shù)可以按照一種自然的順序定義,不用再像 C
那樣為了避免前向聲明而采用常用的從里到外的順序定義。
* 包含文件。造成編譯器運(yùn)行緩慢的原因之一是編譯每個(gè)編譯單元時(shí)都需要重新解析數(shù)量巨大的頭文件。包含文件的工作應(yīng)該采用導(dǎo)入到符號表中的方式來完成。
* 在堆棧上創(chuàng)建對象實(shí)例。在 D
中,所有的類都通過引用來訪問。這樣就不需要復(fù)制構(gòu)造函數(shù)、賦值運(yùn)算符、復(fù)雜的析構(gòu)語義以及同異常處理中的堆棧展開的相互作用。內(nèi)存資源由垃圾收集程序負(fù)責(zé)釋放,其他資源通過使用
D 的 RAII 特征釋放。
* 三字節(jié)碼和雙字節(jié)碼。Unicode 是未來。
* 預(yù)處理程序。現(xiàn)代語言不應(yīng)該需要文本處理,它們應(yīng)該只需要符號處理。
* 非 虛成員函數(shù)。在 C++
中,由累得設(shè)計(jì)者決定一個(gè)函數(shù)是否應(yīng)該是虛函數(shù)。在子類中重寫一個(gè)函數(shù)而忘記在父類中將其更新為虛函數(shù)是一個(gè)常見的(并且非常難以發(fā)現(xiàn)的)編碼錯誤。將所
有成員函數(shù)設(shè)置為虛函數(shù),并由編譯器來判斷函數(shù)是否被重寫、并由此將沒有被重寫的函數(shù)轉(zhuǎn)換為非虛函數(shù)的做法更為可靠。
* 任意長度的位字段。位字段是一種復(fù)雜、低效并且很少用到的特征。
* 支持16位計(jì)算機(jī)。D 從不考慮混合使用遠(yuǎn)/近指針和其它所有用于聲稱好的16位代碼的機(jī)制。D 語言的設(shè)計(jì)假設(shè)目標(biāo)機(jī)器至少擁有32位的平坦內(nèi)存空間。D
將能夠被毫無困難的移植到64位架構(gòu)上。
* 對編譯遍數(shù)的互相依賴。在 C++ 中,需要一個(gè)符號表和各種的預(yù)處理程序命令才能成功的解析一個(gè)源文件。這樣就使預(yù)解析 C++
源碼變得不可能,并且使編寫代碼分析程序和語法制導(dǎo)的編輯器的過程十分難以正確實(shí)現(xiàn)。
* 編譯器的復(fù)雜性。通過降低實(shí)現(xiàn)的復(fù)雜度,這就更有可能出現(xiàn)多個(gè)正確的實(shí)現(xiàn)。
* ‘.’和‘->’之間的區(qū)別。這種區(qū)別其實(shí)很沒有必要。‘.’運(yùn)算符完全可以起到‘->’所起的指針解引用的作用。
D 適合于?
* 經(jīng)常使用 lint 或者類似的代碼分析工具以期在編譯之前減少 bug 的程序員。
* 將編譯器的警告級別調(diào)到最高的人和那些告訴編譯器把警告作為錯誤的人。
* 不得不依靠編程風(fēng)格規(guī)范來避免常見的 C bug 的編程部門經(jīng)理們。
* 認(rèn)定 C++ 面向?qū)ο缶幊趟手Z的功能由于 C++ 太復(fù)雜而不能達(dá)到的人。
* 沉溺于 C++ 強(qiáng)大的表達(dá)力但是被顯式內(nèi)存管理和查找指針 bug 折磨得精疲力盡的人。
* 需要內(nèi)建的測試和驗(yàn)證機(jī)制的項(xiàng)目。
* 開發(fā)百萬行規(guī)模的程序的團(tuán)隊(duì)。
* 認(rèn)為語言應(yīng)當(dāng)提供足夠的特征以避免顯式處理指針的程序員。
* 編寫數(shù)值運(yùn)算程序的程序員。D 擁有眾多直接支持?jǐn)?shù)值計(jì)算的特征,例如直接提供了復(fù)數(shù)類型和擁有確定行為的 NaN 和無窮大。(這些都被加進(jìn)了最新的 C99
標(biāo)準(zhǔn),但是沒有加進(jìn) C++ 中。)
* D 的詞法分析程序和解析程序完全互相獨(dú)立,并且獨(dú)立于語義分析程序。這意味著易于編寫簡單的工具來很好地處理 D
源碼而不用編寫一個(gè)完整的編譯器。這還意味著源碼可以以記號的形式傳遞個(gè)某個(gè)需要它的程序。
D 不適合于?
* 現(xiàn)實(shí)一點(diǎn)說,沒人會把上百萬行的 C 或 C++ 程序用 D 重新寫一遍,因?yàn)?D 不直接兼容 C/C++ 源代碼,D 并不適合于遺留程序。(但是,D
對遺留的 C API 提供了很好的支持。)
* 非常小的程序——腳本或解釋性語言如 Python、DMDScript 或者 Perl 更適合于這種情況。
* 作為第一門程序設(shè)計(jì)語言—— Basic 或者 Java 更適合于初學(xué)者。對于中級到高級的程序員來說,D 是他們優(yōu)秀的第二門語言。
* 語 言純粹主義者。D 是一門實(shí)用的語言,它的每個(gè)特征都是為這個(gè)目的服務(wù)的,D 并沒有想成為一門“完美”的語言。例如,D
擁有可以基本上避免在日常任務(wù)中使用指針的結(jié)構(gòu)和語義。但是 D 仍然支持指針,因?yàn)橛袝r(shí)我們需要打破這條規(guī)則。類似地,D
保留了類型轉(zhuǎn)換,因?yàn)橛袝r(shí)我們需要重寫類型系統(tǒng)。
D 的主要特征
本節(jié)列出了一些更有趣的 D 的特征。
面向?qū)ο缶幊?/strong>
類
D 的面向?qū)ο筇煨詠碜杂陬悺2捎玫睦^承模型時(shí)單根繼承加接口。Object
類為與繼承體系的最頂端,所以所有的類都實(shí)現(xiàn)了一個(gè)通用的功能集合。類通過引用的方式實(shí)例化,所以不需要用于在異常后進(jìn)行清理工作的復(fù)雜代碼。
運(yùn)算符重載
類可以通過重載現(xiàn)有的運(yùn)算符擴(kuò)展類型系統(tǒng)來支持新類型。例如創(chuàng)建一個(gè) bignumber class ,然后重載 +、-、* 和 /
運(yùn)算符,這樣大數(shù)類就可以使用普通的代數(shù)運(yùn)算語法了。
生產(chǎn)力
模塊
源文件同模塊是一一對應(yīng)的。D 不再“包含”帶有聲明的文件的文本,而是“導(dǎo)入”該模塊。不用擔(dān)心多次導(dǎo)入一個(gè)模塊,也不用再把頭文件用 #ifndef/#endif
或者 #pragma once 包起來了。
聲明 vs 定義
C++ 的函數(shù)和類通常需要聲明兩次——聲明位于 .h 頭文件中,定義位于 .c
源文件中。這個(gè)過程易于出錯而且冗長繁瑣。顯然,應(yīng)該只需要程序員編寫一次,而由編譯器提取出聲明信息并將它導(dǎo)入到符號表中。這正是 D 所做的。
示例:
class ABC
{
int func() { return 7; }
static int z = 7;
}
int q;
不再需要單獨(dú)定義成員函數(shù)、靜態(tài)成員、外部聲明之類的,也不需要像這樣煩人的語法:
int ABC::func()
{
return 7;
}
int ABC::z = 7;
extern int q;
注記:當(dāng)然,在 C++ 中,瑣碎的函數(shù)如 { return 7; }
也可以直接寫在聲明處,但是復(fù)雜的函數(shù)就不行了(uframer:雖然從語法上說依然是可以的,但會違反 C++
接口和實(shí)現(xiàn)分離的原則。)。另外,如果有前向引用的話,就必須保證已經(jīng)聲明了被引用的那個(gè)函數(shù)一個(gè)原型。下面的代碼在 C++ 中是不合法的:
class Foo
{
int foo(Bar *c) { return c->bar; }
};
class Bar
{
public:
int bar() { return 3; }
};
但是等價(jià)的 D 代碼就可以正常工作:
class Foo
{
int foo(Bar c) { return c.bar; }
}
class Bar
{
int bar() { return 3; }
}
D 函數(shù)是否被在線化取決于優(yōu)化程序的設(shè)置。