D語言由來,D語言簡介,為什麼需要D語言,D的主要目標,簡述,從C/C++保留而來的特徵,廢棄的特徵,預處理程式,多重繼承,名字空間,前向聲明,包含檔案,在堆疊上創建對象實例,適合於,不適合於,主要特徵,面向對象編程,運算符重載,模組,關聯數組,真正的 typedef,Bit 類型,函式,數組,字元串,資源管理,RAII,性能,可靠性,契約,單元測試,調試特徵和語句,異常處理,同步,對健壯性技術的支持,兼容性,項目管理,
D語言由來 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以及將
代碼移植 到 D 並非難事,從 C/C++ 轉向 D 很自然,程式設計師不必從頭學起。
使用 D 並不意味著程式設計師會如
Java 或者
Smalltalk 那樣被嚴格限制在
虛擬機 上。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++ 編譯器了。
預處理程式 宏處理是一種擴展語言的簡單方法,它可以給語言加入某些語言本不支持的(對於符號調試器不可見的)特徵。
條件編譯 、使用 #include 分層的文本、宏、符號連線等,本質上構成了兩種難以區分兩種語言的融合體,而不是一種語言。更糟的是(或許是最好的),C 預處理程式是一種十分原始的宏語言。是停下來的時候了,看看預處理程式是用來做什麼的,並將這些功能直接設計到語言內部。
多重繼承 它是一種擁有飽受爭議的價值的複雜特徵。它很難用一種高效的方式實現,而且在
編譯器 實現它時很容易出現各種 bug 。幾乎所有的 MI 的功能都能夠通過使用
單根繼承 加接口和聚集的方式實現。而那些只有 MI 才能支持的功能並不能彌補它帶來的副作用。
名字空間 當連結獨立開發的代碼時,可能會發生名字的衝突,名字空間就是解決這個問題的一種嘗試。模組的概念更簡單並且工作得更好。
標記名字空間。這是 C 的一個糟糕的特徵,結構的標記名稱位於一個同其它符號不同的
符號表 中。C++ 試圖合併標記名字空間和正常的名字空間,但同時還要維持對遺留 C 代碼的向後兼容性。造成的結果是不可列印。
前向聲明 C
編譯器 在語義上只知道什麼東西實在詞法上位於當前狀態之前的。C++ 進行了一點點擴展,類中的成員可以依賴於它之後聲明的類成員。D 更進一步,得到了一個合情合理的結論,前向聲明根本就沒有存在的必要。函式可以按照一種自然的順序定義,不用再像 C 那樣為了避免前向聲明而採用常用的從裡到外的順序定義。
包含檔案 造成
編譯器 運行緩慢的原因之一是編譯每個
編譯單元 時都需要重新解析數量巨大的頭檔案。包含檔案的工作應該採用導入到
符號表 中的方式來完成。
在堆疊上創建對象實例 在 D 中,所有的類都通過引用來訪問。這樣就不需要
複製構造函式 、
賦值運算符 、複雜的析構語義以及同
異常處理 中的堆疊展開的相互作用。記憶體資源由垃圾收集程式負責釋放,其他資源通過使用 D 的 RAII 特徵釋放。
預處理程式。現代語言不應該需要文本處理,它們應該只需要符號處理。
非
虛成員函式 。在 C++ 中,由累得設計者決定一個函式是否應該是
虛函式 。在子類中重寫一個函式而忘記在父類中將其更新為虛函式是一個常見的(並且非常難以發現的)編碼錯誤。將所有成員函式設定為虛函式,並由
編譯器 來判斷函式是否被重寫、並由此將沒有被重寫的函式轉換為非虛函式的做法更為可靠。
任意長度的
位欄位 。位欄位是一種複雜、低效並且很少用到的特徵。
支持16位計算機。D 從不考慮混合使用遠/近
指針 和其它所有用於聲稱好的16位代碼的機制。D 語言的設計假設目標機器至少擁有
32位 的平坦記憶體空間。D 將能夠被毫無困難的移植到
64位 架構上。
對編譯遍數的互相依賴。在 C++ 中,需要一個
符號表 和各種的預處理程式命令才能成功的解析一個源檔案。這樣就使預解析 C++ 源碼變得不可能,並且使編寫代碼分析程式和語法制導的編輯器的過程十分難以正確實現。
編譯器 的複雜性。通過降低實現的複雜度,這就更有可能出現多個正確的實現。
‘.’和‘->’之間的區別。這種區別其實很沒有必要。‘.’
運算符 完全可以起到‘->’所起的
指針 解引用的作用。
適合於 不適合於 現實一點說,沒人會把上百萬行的 C 或 C++ 程式用 D 重新寫一遍,因為 D 不直接兼容 C/C++
原始碼 ,D 並不適合於遺留程式。(但是,D 對遺留的 C API 提供了很好的支持。)
作為第一門
程式設計語言 —— Basic 或者 Java 更適合於初學者。對於中級到高級的程式設計師來說,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 函式是否被線上化取決於最佳化程式的設定。
模板
D 模板提供了一種提供范型編程和偏特化能力的簡潔的方法。
關聯數組 關聯數組 是索引可以為任意類型的數組,不像普通數組那樣必須使用整數作為索引。本質上,關聯數組就是散列表。關聯數組使構建快速、高效、無錯的
符號表 變得容易了。
真正的 typedef C 和 C++ 的 typedef 實際上是類型 別名 ,因為它不會引入新的類型。D 實現了真正的 typedef :
typedef int handle;
實實在在地創造了一個新類型 handle 。D 同樣會對 typedef 引入的類型進行類型檢查,並且 typedef 也參與
函式重載 的決策。例如:
int foo(int i); int foo(handle h);
Bit 類型 機器上最基本的數據類型是位,因此 D 直接支持 bit 數據類型。它在創建位
數組 時最有用:
bit[] foo;
函式 函式可以嵌套在其他函式內。這對於代碼分解、局部性以及函式閉包技術都具有很高的價值。
函式文字量
動態閉包
嵌套函式和類成員函式可以被稱為閉包(也被稱為委託),它們可使范型編程更為容易並保證
類型安全 。
In, Out 和 Inout 參數
這幾個
修飾符 不只能使函式更為易於理解,還能避免使用
指針 而不會影響代碼的功能,另外這也會提高
編譯器 幫助程式設計師找到編碼問題的可能性。
這些修飾符使 D 能夠直接同更多的外部 API 對接。也就無需使用“接口定義語言”(IDL)之類的東西了。
數組 數組本身並不帶有數組結構的信息,它們必須另外存儲和傳遞。一個經典的例子就是傳遞給 main(int argc, char *argv[]) 函式的 argc 和 argv 參數。(在 D 中,main 被聲明為 main(char[][] args) 。)
數組不是一等公民。當一個數組被傳遞給函式時,他被轉換為
指針 ,儘管那個原型令人迷惑地聲稱它是一個數組。當發生類型轉換時,所有的數組類型信息也就丟失了。
C數組的大小不可改變。這意味著即使最簡單的聚集如
堆疊 都必須用一個複雜的類構造。
C
數組 沒有邊界檢查,因為它們根本不知道數組邊界是多少。
數組聲明中的 [] 位於標誌符之後。這使得聲明如一個指向數組的指針這樣的東西都需要複雜難懂語法:
int (*array)[3];
在 D 中,數組的 [] 位於左側:
int[3] *array; // 聲明了一個指向含有 3 個 int 的數組的
指針 long[] func(int x); // 聲明了一個返回含有 long 數據的數組
顯然這更易於理解。
字元串 在 C 和 C++ 中,對字元串的操作是如此的頻繁,而又如此的笨拙,以至於最好還是由語言本身來支持它比較好。現代語言都處理字元串連線、複製等等操作,D 語言也提供了這些支持。字元串是直接由經過改進的
數組 來處理的。
資源管理 垃圾收集
D 的
記憶體分配 完全採用垃圾收集。經驗告訴我們,C++ 中的很多複雜特徵都是用於處理
記憶體釋放 的。有了垃圾收集,語言就變得簡單多了。
有一種看法認為垃圾收集是給那些懶惰、初級的程式設計師準備的。我還記得那些對 C++ 的評論,畢竟,沒有什麼 C++ 能做而 C 不能做的,或者這對彙編來說也一樣。
採用垃圾收集可以避免 C 和 C++ 中必需的乏味的、易於出錯的記憶體分配和追蹤代碼。這不只意味著更少的開發時間和更低的維護費用,還意味著程式運行得更快!
當然,可以在 C++ 中使用垃圾收集程式,我已經在我自己的項目中使用它了。C++ 對垃圾收集程式並不友好,這也造成了 C++ 中垃圾收集的低效。很多
運行時庫 的代碼都不能同來垃圾收集程式一同工作。
顯式記憶體分配
儘管 D 是一種採用垃圾收集的語言,還是可以重寫某個類的 new 和 delete 操作以採用一個定製的分配器。
RAII RAII 是一種管理資源分配和釋放的現代
軟體開發技術 。D 以一種可控的、可預測的方式支持 RAII ,它是獨立於垃圾收集程式的回收周期的。
性能 輕量級聚集
D 支持簡單的 C 風格的結構,既保證了對 C數據結構的兼容性,也是因為有時採用類有殺雞用牛刀之嫌。
設備驅動程式 、高性能系統程式、
嵌入式系統 和某些特殊的代碼需要使用彙編語言完成任務。儘管 D 的實現不一定要實現內聯彙編,它也仍被定義為語言的一部分。他可以滿足絕大多數使用彙編語言的需要,這樣就不需要單獨的
彙編程式 或者使用
DLL 了。
許多的 D 實現同時也實現那些類似於 C 的支持
I/O 連線埠操作、直接訪問浮點硬體等內部功能的內函式。
可靠性 現代的語言應該竭盡所能地幫助程式設計師避免出錯。語言提供的幫助有多種形式:從易於使用更為健壯的技術,到有
編譯器 指出明顯出錯的代碼,到運行時檢查。
契約 契約式編程(由 B. Meyer 發明)是一種用於保證程式正確性的革命性的技術。D 版本的 DBC 包括函式先驗條件、函式後驗條件、雷不變數和斷言契約。
單元測試 可以給一個類加入
單元測試 ,這樣
測試程式 就能在程式啟動時自動運行。這樣就能夠在每次構建時都驗證類是否實現了他所應完成的功能。單元測試構成了
原始碼 的一部分。創建單元測試成為了類開發過程中的自然的一部分,而不是將完成的代碼直接拋給測試小組。
單元測試可以使用其它語言完成,但是其結果看起來有一種七拼八湊的感覺,而且你採用的那種語言很可能並不兼容這個概念。
單元測試 是 D 的一個主要特徵。對於
庫函式 來說,單元測試已經被證明是十分有效的。它既可以保證函式工作正常,也可以演示如何使用這些函式。
考慮大量的可以從網上下載的 C++ 庫和應用程式代碼。其中有“幾個”是帶有驗證測試的?更不要奢望單元測試了?少於 1% ?通常的做法是,如果它們能通過編譯,我們就假定它是正確的。而且我們不知道變異過程中給出的警告到底是真正的錯誤還是瞎嘮叨。
契約式編程和單元測試使 D 為編寫可信賴、健壯的系統程式的最好的語言。單元測試還是我們能夠粗略但快速地估計你從未經手的 D 代碼片斷的質量——如果沒有單元測試和契約式編程,每人會幹這種事。
調試特徵和語句 調試已經成為了語言語法的一部分了。可以在編譯時決定是否使用這些代碼,再也不用使用宏或者
預處理命令 了。調試語法提供了一種持續的、可移植的、易於理解的識別調試代碼的方法,使程式設計師既能夠生成帶有調試代碼的二進制版本,也能夠生成沒有調試代碼的二進制版本。
異常處理 D 採用了更為高級的 try-catch-finally 模型而不是原來的 try-catch 模型。沒有必要只是為了利用
析構函式 實現 finally 語義而構造一個傀儡對象。
同步 因為多執行緒編程已經越來越成為主流,所以 D 提供了構建多執行緒程式的
原語 。同步既可以作用在方法上,也可以作用在對象上。
synchronized int func() { . }
同步方法一次只允許一個執行緒執行。
同步語句將在語句塊周圍插入一個
互斥體 ,控制對象或全局的訪問。
對健壯性技術的支持 使用動態數組而不是指針
使用對變數的引用而不是指針
使用對對象的引用而不是指針
使用垃圾收集而不是顯式記憶體分配
內建執行緒同步原語
不再有宏給你的代碼來那么一下子
使用內聯函式而不是宏
在很大程度上減少了使用指針的需要
整型的大小是明確的
不用再擔心 char 類型是否有符號了
不必再分別在源檔案和頭檔案中重複地寫聲明了
為調試代碼提供了顯式的解析支持
編譯時檢查
更強的類型檢查
需要進行顯式初始化
不允許出現空的(只由‘;’的)循環體
廢棄過時的 API
運行時檢查
數組邊界檢查
switch 語句中的未定義 case 語句異常
記憶體耗盡異常
in、out 和類不變數提供了對契約式編程的支持
兼容性 運算符優先權和求值規則
D 保留了 C 的運算符和它們的優先權、求值的規則和
類型提升 規則。這就避免了由於同 C 的語義不同而造成的微妙的難以發現的 bug 的出現。
直接訪問 C API
D 不支擁有同 C 類型對應的類型,它還提供了直接訪問 C 函式的能力。完全沒有必要編寫封裝函式和參數變換器,也沒有必要逐一地複製聚集類型的成員。
支持所有的 C數據類型
使對 C API 或者現有的 C 庫代碼的接口成為可能。D 支持結構、聯合、枚舉、
指針 和所有的 C99 類型。D 還擁有設定結構成員對齊方式的能力,這樣就可以保證同外部導入的
數據格式 的兼容。
作業系統異常處理
D 的
異常處理 機制將在應用程式中利用底層作業系統提供的異常處理方式。
使用現成的工具
D 生成標準的目標檔案格式,這樣就能夠使用標準的
彙編程式 、
連結器 、
調試器 、
性能分析 工具、可執行程式壓縮程式和其他的分析程式,還能夠同其他語言編寫的代碼相連結。
項目管理 D 對從同一份源碼生成多個版本的程式提供了內建的支持。它替代了 C 預處理程式的 #if/#endif 技術。
廢棄
隨著代碼不停的演進,一些舊的庫代碼會被更新,更好的版本代替。同時舊的版本必須可用以支持舊的客戶代碼,舊的版本可以被標記為 廢棄的 。可以通過
編譯器 的一個選項來判斷使用廢棄版本代碼的版本是否是合法的,這樣一來負責維護的程式設計師就可以更為輕鬆的判斷哪裡是依賴於已經被廢棄的特徵的。
沒有警告
D編譯器不會為可疑的
代碼生成 警告。代碼要么可以被編譯器接受,要么不能被編譯器接受。這樣就不會引起有關哪些警告確實是錯誤而那些不是的爭論,也就沒有如何處理它們的爭論了。對編譯器警告的需求本身就是不好的語言設計的表現。