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