C++ 程式設計 - 第 1 章

C++ (發音為 "see plus plus") 是一種 通用 的,多正規化 的,靜態型別 的,自由格式 的 程式語言,支援過程式、面向物件、泛型和(最近)函數語言程式設計正規化,並且以其在程式碼中實現低成本抽象而聞名。如果你對上述任何概念不熟悉,請不要擔心,我們將在後面的章節中介紹它們。
在 1990 年代,C++ 逐漸成為最流行的計算機程式語言之一,根據 TIOBE 指數,它仍然是第四大流行語言。[1] C++ 最初的設計重點是 系統程式設計,但其功能也使其成為建立終端使用者應用程式的吸引人的語言,尤其是那些資源受限或需要非常高的 效能 的應用程式。C++ 廣泛用於遊戲開發、Web 客戶端/伺服器端、金融應用程式的後端和機器人技術。

Bjarne Stroustrup,來自 貝爾實驗室 的計算機科學家,是 1980 年代 C++(最初名為“帶類的 C”)的設計者和最初的實現者,它是對 C 程式語言 的增強。C,它也是在貝爾實驗室為了實現 Unix 作業系統而由 丹尼斯·裡奇 建立的,它讓使用者能夠以比 組合語言 (ASM) 更高的概念層次控制硬體,但表達能力仍然有限。Stroustrup 決定將 面向物件 的 Simula 語言中的程式組織功能與 C 的高效硬體資源利用能力結合起來。增強從新增 類 和 虛擬函式 等 名稱空間、運算子過載、模板 和 異常處理 等許多功能開始,這些以及其他功能將在本書中詳細介紹。C++ 的幾個功能後來被 C 採用,包括用於建立程式中不可變值的 const 關鍵字,inline 函式,在 for 迴圈 中的宣告,以及 C++ 風格的註釋(使用//符號)。
C++ 程式語言 是一種由 ANSI(美國國家標準協會)、BSI(英國標準協會)、DIN(德國國家標準化機構)以及其他幾個國家標準化機構認可的標準,並於 1998 年由 ISO(國際標準化組織)批准為 ISO/IEC 14882:1998,儘管更常被稱為 C++98 或簡稱 C++。該標準包含兩個部分:核心語言和標準庫;後者包括 標準模板庫 和 標準 C 庫(ANSI C 89)。
2003 年版本,ISO/IEC 14882:2003,被稱為 C++03,重新定義了標準語言作為一個單一專案。STL(“標準模板庫”)早於 C++ 的標準化(最初是在 Ada 中實現的)成為標準的組成部分,並且是符合標準的實現的必要條件。
從 2004 年開始,標準委員會(包括 Bjarne Stroustrup)制定了新標準修訂版細節,C++11(以前稱為 C++0x)於 2011 年 8 月 12 日獲得批准。C++11 使語言更加高效、易於使用,並且為標準庫添加了更多功能。C++14 的規範於 2014 年 12 月 15 日釋出,與 C++11 相比,其更改較小,並且對該標準的編譯器支援也很快跟進。一些 表格 提供了所謂 現代 C++ 功能的編譯器支援情況。
許多其他 C++ 庫並不屬於標準,一個流行的例子是 Boost。此外,用 C 編寫的非標準庫通常可以被 C++ 程式使用。
- C++ 原始碼示例
// 'Hello World!' program
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
傳統上,人們在學習一門新語言時編寫的第一個程式被稱為“Hello World”,因為它只做一件事,就是簡單地顯示“Hello World”這句話,同時在這個過程中揭示有關該語言的基本資訊。 Hello World 解釋(在 示例附錄 中)提供了對這段程式碼的詳細解釋,其中可以看出這裡提到的 C++ 的幾個元素,包括類似 C 的語法和標準庫的使用。
在你開始學習如何使用C++編寫程式之前,瞭解一些你可能會遇到的關鍵概念非常重要。這些概念並不侷限於C++,但對理解計算機程式設計總體來說很有幫助。有其他程式語言經驗的讀者可能想快速瀏覽一下本節,或直接跳過。
如今,有許多不同型別的程式在使用。從你用來確保所有程式正常執行的作業系統,到你用來娛樂的電子遊戲和音樂應用程式,程式可以完成許多不同的目的。所有程式(也稱為軟體或應用程式)的共同點是,它們都由一系列指令組成,這些指令以某種形式用某種程式語言編寫。這些指令告訴計算機做什麼,以及通常如何去做。程式可以包含任何內容,從解決數學問題的指令,到在遊戲中射擊遊戲角色時應該如何表現的指令。計算機將按照程式的指令,從頭到尾,一次執行一條指令。
所有計算機程式(或者大多數程式)的另一個共同點是,它們解決問題並執行任務。向世界問好。在螢幕上繪製一個按鈕。計算 26*78。駕駛汽車。幸運的是,計算機必須學習如何執行這些任務。換句話說,它們必須被程式設計。
為什麼不呢?這是決定學習任何事物的最明確方法。雖然學習總是好的,但選擇學習什麼更重要,因為這將是你優先考慮的任務。這個問題的另一方面是,你將投入一些時間來獲得新的技能。你必須決定這將如何使你受益。檢查你的目標,比較類似的專案,或看看程式設計市場需要什麼。無論如何,你懂得的程式語言越多越好。
C++不是理想的第一語言。但是,如果你願意對C++投入不止一時的興趣,那麼你甚至可以將其作為你的第一語言來學習。確保花一些時間瞭解不同的正規化,以及為什麼 C++ 是一種多正規化或混合語言。
如果你只是為了在你的履歷上添上另一筆而學習,也就是說,你只願意付出足夠的努力來理解它的主要怪癖,並瞭解它的一些黑暗角落,那麼你最好先學習另外兩種語言。這將闡明C++在程式設計方法上有哪些特別之處。你應該選擇一種命令式語言和一種面嚮物件語言。C 可能是前者的最佳選擇,因為它具有良好的市場價值,並且與C++有直接的關係,儘管 ASM 也是一個不錯的替代品。對於後者,Java 是一個不錯的選擇,主要是因為它與 C++ 共享很多語法,但它不支援指令式程式設計。閱讀語言比較部分,以更好地理解它們之間的關係。
雖然學習 C 不是理解C++的必要條件,但你必須知道如何使用命令式語言。C++不會讓你很容易理解一些更深層的概念,因為在C++中,你作為程式設計師,擁有更大的自由度。在 C++ 中,有許多方法可以做同一件事。理解選擇哪些選項將成為掌握這種語言的基石。
如果你僅僅對學習面向物件程式設計感興趣,你不應該學習C++。C++ 對物件提供了一些支援,但它仍然不是真正面向物件的,因此使用的術語和解決問題的思路將使其更難學習和掌握這些概念。如果你真的對面向物件程式設計感興趣,你應該學習Smalltalk。
與所有語言一樣,C++有其特定的應用範圍,它可以在其中真正發光。C++ 比 C 和 Java 更難學習,但比兩者都更強大。C++使你能夠從在 C 或其他低階語言中必須處理的小事中抽象出來,但會賦予你比 Java 更多的控制權和責任。由於它不會提供類似高階語言中可以獲得的預設功能,你將不得不搜尋和檢查這些功能的幾個外部實現,並自由選擇最適合你目的的實現(或者實現你自己的解決方案)。
從最基本的層面上來說,"程式語言"是人(程式設計師)和計算機之間的一種溝通方式。程式設計師使用這種溝通方式來向計算機發出指令。這些指令稱為“程式”。
就像我們用來互相溝通的許多自然語言一樣,程式設計師可以使用許多語言來與計算機溝通。每種程式語言都有自己的一套詞語和規則,稱為該語言的語法。如果你要編寫程式,你必須遵循你所使用的語言的語法,否則你就無法被理解。
程式語言通常可以分為兩類:低階和高階,這兩個概念都將介紹給你,以及它們與 C++ 的關係。

計算機“語言”中較低的級別是
機器碼(也稱為二進位制)是低階語言的最低形式。機器碼由一串 0 和 1 組成,它們組合在一起形成計算機可以執行的有意義的指令。如果你看一頁二進位制程式碼,就會明白為什麼二進位制程式碼永遠不是編寫程式的實用選擇;什麼樣的人才能記住一堆 1 和 0 的字串代表什麼意思呢?
組合語言(也稱為 ASM),在從低階到高階的級別上,正好高於機器碼。它是計算機執行的機器語言指令的人類可讀翻譯。例如,程式設計師不是透過二進位制表示(0 和 1)來引用處理器指令,而是使用更易於記憶的(助記符)形式來引用這些指令。這些助記符通常是表示相應指令動作的字母的簡短組合,例如“ADD”表示加法,“MOV”表示將值從一個地方移動到另一個地方。
你不需要理解組合語言就能用 C++ 程式設計,但瞭解“幕後”發生了什麼總是有幫助的。學習組合語言也能讓你作為程式設計師擁有更多控制權,並幫助你除錯和理解程式碼。
由於大多數程式設計任務的大小和複雜性,使用高階語言格式的優點遠遠超過任何缺點,這些優點包括
- 高階程式結構:迴圈、函式和物件在低階語言中的可用性有限,因為它們的存在本身就被認為是“高階”功能;也就是說,每個結構元素必須進一步翻譯成低階語言。
- 可移植性:高階程式可以在不同的計算機上執行,而無需或只需很少的修改。低階程式通常使用僅在某些處理器上可用的專用函式,並且必須重新編寫才能在另一臺計算機上執行。
- 易用性:在組合語言中需要許多行程式碼才能完成的任務,可以用高階程式語言中的庫中的幾個函式呼叫來簡化。例如,Java 是一種高階程式語言,它可以用大約五行程式碼繪製出一個功能齊全的視窗,而等效的組合語言則至少需要四倍的程式碼量。
高階語言用更少的程式碼做更多的事情,雖然有時會損失效能,並且程式設計師的自由度會降低。它們還嘗試使用英語單詞的形式,這些單詞可以被沒有或幾乎沒有程式設計經驗的普通人閱讀和理解。用這些語言之一編寫的程式有時被稱為“人類可讀程式碼”。一般來說,抽象使得學習程式語言更容易。
不過,沒有一種程式語言是用什麼人都能理解的自然語言(比如“純英語”)編寫的(儘管 BASIC 和 COBOL 接近,而且有人正在奧斯莫西安教團的純英語編譯器和整合開發環境中為此努力,該環境完全用純英語編寫,那麼純英語的定義就需要討論了)。無論如何,由於編寫程式語言(構造的和形式的語言)時需要對書面表達進行簡化和控制,因此程式的文字有時被稱為“程式碼”,更具體地說被稱為“原始碼”。這將在本書的程式碼部分中詳細討論。
需要記住的是,雖然有些詞語(指令)是用英語寫的(主要是為了方便),但使用的語言是不同的(通常有充分的理由,否則有人會創造一種新的程式語言),除此之外,上面段落的其餘部分可能只有在你開始構建解析器、語言和編譯器時才重要。語言級別越高,它就越努力地透過支援程式碼的可移植性和透過表示式和結構的複雜性增加來提高人類的可理解性,來解決對硬體(CPU、協處理器、暫存器數量等)的抽象問題。
請記住,這種分類方案正在不斷發展。C++ 仍然被認為是一種高階語言,但隨著新語言(Java、C#、Ruby 等)的出現,C++ 開始與 C 等低階語言歸為一類。
由於計算機只能理解機器程式碼,因此人類可讀的程式碼必須被解釋或翻譯成機器程式碼。
一個直譯器是一個程式(通常用更低階的語言編寫),它一次解釋程式指令,將其轉換為直譯器執行時要執行的命令。通常每個指令包含一行文字或提供其他明確的方法來區分每個指令,並且每次執行程式時都必須重新解釋程式。
一個編譯器是一個程式,用於將原始碼一次一條指令地翻譯成機器程式碼。翻譯成機器程式碼可能涉及將編譯器理解的一條指令拆分為多個機器指令。這些指令只翻譯一次,之後機器可以理解並直接執行這些指令,無論何時被指示執行。本書的編譯器部分對 C++ 編譯器進行了全面的分析。
用於指示計算機的工具可能不同,但是無論使用什麼語句,幾乎每種程式語言都將支援實現以下功能的結構
- 輸入
- 輸入是指從鍵盤、滑鼠或有時是其他程式等裝置獲取資訊的行為。
- 輸出
- 輸出與輸入相反;它將資訊提供給計算機顯示器或其他顯示裝置或程式。
- 數學/演算法
- 所有計算機處理器(計算機的大腦)都具有執行基本數學運算的能力,每種程式語言都以某種方式告訴它執行這些運算。
- 測試
- 測試涉及告訴計算機檢查某個條件,並在該條件為真或假時執行某些操作。條件是程式設計中最重要的概念之一,所有語言都有一些測試條件的方法。
- 重複
- 重複執行某些操作,通常伴隨一些變化。
本書的語句部分提供了對 C++ 語言結構的進一步分析和說明。
信不信由你,這就是全部。你使用過的每個程式,無論多麼簡單或複雜,都是由功能組成的,這些功能或多或少類似於這些功能。因此,描述計算機程式設計的一種方法是將一個大型複雜的任務分解成越來越小的子任務,直到最終每個子任務都足夠簡單,可以用這些功能之一來執行。
C++ 主要編譯而不是解釋(有一些 C++ 直譯器),然後在稍後“執行”。雖然這看起來可能很複雜,但在後面你會看到它有多麼容易。
正如我們在介紹 C++ 部分中看到的那樣,C++ 是從 C 演變而來,增加了抽象級別(因此我們可以正確地說 C++ 的級別高於 C)。我們將學習這些差異的細節,在本書的程式設計正規化部分中,對於那些已經瞭解其他語言的人來說,應該看看程式語言比較部分。
一個程式設計正規化是基於不同概念的程式設計模型,它塑造了程式設計師設計、組織和編寫程式的方式。一個多正規化程式語言允許程式設計師選擇特定的單一方法或混合不同程式設計正規化的部分。C++ 作為一種多正規化程式語言,支援使用程序式程式設計或面向物件程式設計的單一或混合方法,並將通用程式設計甚至函數語言程式設計概念的利用混合在一起。
程序式程式設計可以定義為指令式程式設計的一種子型別,它是一種基於過程呼叫的程式設計正規化,其中語句被結構化為過程(也稱為子例程或函式)。過程呼叫是模組化的,受範圍約束。過程式程式由一個或多個模組組成。每個模組由一個或多個子程式組成。模組可能包含過程、函式、子例程或方法,具體取決於程式語言。過程式程式可能包含多個級別或範圍,子程式定義在其他子程式中。每個範圍可以包含外部範圍不可見的名稱。
程序式程式設計相對於簡單的順序程式設計提供了許多好處,因為過程式程式碼
- 更易於閱讀和維護
- 更靈活
- 有利於良好程式設計的實踐
- 允許模組以程式碼庫的形式再次使用。
型別是指計算機語言如何處理其變數,以及如何透過型別區分變數。變數是程式在執行期間使用的值。這些值可以改變;它們是變數,因此得名。靜態型別通常會生成執行速度更快的編譯程式碼。當編譯器知道正在使用的確切型別時,它可以更輕鬆地生成執行正確操作的機器程式碼。在 C++ 中,變數需要在使用之前定義,以便編譯器知道它們是什麼型別,因此是靜態型別的。不是靜態型別的語言被稱為動態型別語言。
靜態型別通常在編譯時更可靠地發現型別錯誤,從而提高編譯程式的可靠性。簡單地說,這意味著“圓形釘子不適合方形孔”,因此當型別導致歧義或不相容使用時,編譯器會報告它。然而,程式設計師對型別錯誤的普遍性和靜態型別可以捕獲多少錯誤存在爭議。靜態型別支持者認為,經過型別檢查的程式更可靠,而動態型別支持者則指出已經證明可靠的動態程式碼和小型錯誤資料庫。因此,靜態型別的價值隨著型別系統強度的增加而增加。
靜態型別系統比約束較弱的語言結構更約束強大的語言結構。這使得強大的結構更難使用,並將選擇“正確工具解決問題”的負擔放在程式設計師的肩上,否則他們可能傾向於使用最強大的工具。選擇過於強大的工具可能會導致額外的效能、可靠性或正確性問題,因為對可以從強大的語言結構中期望的屬性存在理論限制。例如,不加選擇地使用遞迴或全域性變數可能會導致有據可查的不利影響。
靜態型別允許構建庫,這些庫不太可能被其使用者意外誤用。這可以用作一種額外的機制來傳達庫開發者的意圖。
型別檢查是在編譯時或執行時驗證和強制型別約束的過程。編譯時檢查,也稱為靜態型別檢查,是在編譯程式時由編譯器執行的。執行時檢查,也稱為動態型別檢查,是在程式執行時由程式執行的。如果型別系統確保型別之間的轉換必須是有效的或導致錯誤,則稱程式語言為強型別。另一方面,弱型別語言不提供此類保證,並且通常允許型別之間的自動轉換,這些轉換可能沒有有用的目的。C++處於兩者之間,允許混合使用自動型別轉換和程式設計師定義的轉換,從而允許在解釋一種型別為另一種型別時具有幾乎完全的靈活性。將一個型別的變數或表示式轉換為另一個型別稱為型別轉換。
面向物件程式設計
[edit | edit source]面向物件程式設計可以看作是程序式程式設計的擴充套件,其中程式由稱為物件的各個單元的集合組成,這些單元具有不同的目的和功能,對實現的依賴有限或沒有依賴。例如,汽車就像一個物件;它可以將你從 A 點帶到 B 點,而無需知道汽車使用的是什麼型別的發動機或發動機的運作方式。面向物件的語言通常提供一種方法來記錄一個物件可以做什麼和不能做什麼,就像駕駛汽車的說明一樣。
物件和類
[edit | edit source]一個物件由成員和方法組成。成員(也稱為資料成員、特徵、屬性或特性)描述了物件。方法通常描述與特定物件關聯的操作。將物件視為名詞,其成員為描述該名詞的形容詞,其方法為該名詞可以執行的動詞。
例如,跑車是一個物件。它的一些成員可能是它的高度、重量、加速度和速度。物件的成員只儲存關於該物件的資料。跑車的一些方法可能是“駕駛”、“停車”、“比賽”等等。方法只有與跑車相關聯時才有意義,成員也是如此。
讓我們構建跑車物件的“藍圖”被稱為類。類不會告訴我們跑車的速度有多快,或者它的顏色是什麼,但它確實告訴我們跑車將有一個代表速度和顏色的成員,並且它們將分別是數字和單詞。該類還為我們規劃了方法,告訴汽車如何停車和駕駛,但這些方法僅使用藍圖無法採取任何行動 - 它們需要一個物件才能產生影響。
C++ 中的類與 C 中的結構相同;區別在於類使用者可以透過 private 選項隱藏資料。在 C++ 中,物件是類的例項,它被視為一個內建變數,該變數包含許多值。
封裝
[edit | edit source]封裝,即資訊隱藏(來自使用者)的原則,是隱藏類資料結構並允許透過公共介面更改資料的過程,在公共介面中,對傳入值的有效性進行檢查,因此它不僅允許在物件中隱藏資料,還允許隱藏行為。這可以防止介面的客戶端依賴於將來可能更改的實現部分,從而允許更容易地進行這些更改,即在不更改客戶端的情況下進行更改。在現代程式語言中,資訊隱藏原則以多種方式體現出來,包括封裝和多型性。
繼承
[edit | edit source]繼承描述了兩種(或更多)型別或物件的類之間的關係,其中一種被稱為另一種型別的“子型別”或“子類”;因此,“子類”物件被認為繼承了父類的特徵,從而允許共享功能。這使程式設計師可以重用或減少程式碼,並簡化軟體的開發和維護。
繼承通常也被認為包括子型別化,其中一種型別的物件被定義為另一種型別的更專門的版本(見Liskov 替換原則),儘管非子型別化繼承也是可能的。
繼承通常透過描述由其繼承關係建立的繼承層次結構(也稱為繼承鏈)中的物件類來表示,這是一種樹狀結構。
例如,可以建立一個名為“Mammal”的變數類,它具有進食、繁殖等特徵;然後定義一個子型別“Cat”,它繼承了這些特徵,而無需顯式地對其進行程式設計,同時添加了“追逐老鼠”等新特徵。這允許在不同型別的物件之間表達一次公共點並重復使用多次。
在 C++ 中,我們可以擁有與其他類相關的類(一個類可以透過使用更舊的、預先存在的類來定義)。這導致了一種情況,即一個新類具有舊類的所有功能,並另外引入了自己的特定功能。我們這裡指的是派生,即一個給定類是另一個類,而不是組合,其中一個給定類包含另一個類。
當我們討論類(和結構)繼承時,將進一步解釋此 OOP 屬性,具體請參閱本書的類繼承部分。
如果想要同時使用多個完全正交的層次結構,例如允許“Cat”從“Cartoon character”和“Pet”以及“Mammal”繼承,那麼我們正在使用多重繼承。
多重繼承
[edit | edit source]多重繼承是一個類可以繼承兩個或多個類(分別稱為其基類、父類、祖先類或超類)屬性的過程。
本書的C++ 類繼承部分將更詳細地介紹這一點。
多型性
[edit | edit source]多型性允許為多個相關但不同的目的重用一個名稱。多型性的目的是允許使用一個名稱來表示一個通用類。根據資料的型別,執行通用情況的特定例項。
多型性概念更廣泛。只要我們使用兩個具有相同名稱但實現不同的函式,就會存在多型性。它們也可能在介面上有所不同,例如,透過接受不同的引數。在這種情況下,選擇哪個函式由過載解析來完成,並在編譯時執行,因此我們將其稱為靜態多型性。
動態多型性將在類部分中深入介紹,我們將討論它在派生類中重新定義方法時的使用。
泛型程式設計
[edit | edit source]泛型程式設計或多型性是一種程式設計風格,強調允許一個值採用不同的型別的方法,只要保持某些約定,例如子型別和簽名。簡單來說,泛型程式設計基於找到高效演算法的最抽象表示。模板普及了泛型的概念。模板允許在不考慮最終使用的型別的情況下編寫程式碼。模板是在標準模板庫 (STL)中定義的,泛型程式設計是在 C++ 中引入的。
自由格式
[edit | edit source]自由格式是指程式設計師如何編寫程式碼。基本上,除了 C++ 的語義規則外,沒有關於如何選擇編寫程式的規則。只要是合法的 C++,任何 C++ 程式都應該可以編譯。
C++ 的自由格式特性被一些程式設計師用來(或濫用,取決於你的觀點)編寫混淆的 C++(故意寫成難以理解的程式碼)。在合適的語境下,這也可能被視為精湛的技藝(非功能性但對語言的藝術性控制),但總的來說,混淆的使用只被視為一種原始碼安全機制,確保原始碼更刻意地難以被第三方分析、複製或使用。透過對編譯器足夠的瞭解,原始碼也可以被設計為在編譯後的形式中保留“水印”,從而允許追蹤到原始的原始碼。
不存在完美的語言。一切取決於資源(工具、人員,甚至可用時間)和目標。對於更廣泛地瞭解其他語言及其演變,這個主題超出了本書的範圍,還有許多其他作品可供參考,包括計算機程式設計華夏公益教科書。
本節旨在為已有經驗的人提供快速入門,幫助他們瞭解 C++ 語言的特殊特性,並展示其獨特之處。
理想語言取決於具體問題。所有程式語言都是為了表達解決問題的演算法的一般機制而設計的。換句話說,它是一種語言——而不是簡單的表示式——因為它能夠表達對多個特定問題的解決方案。
程式語言中泛化的程度各不相同。有領域特定語言 (DSL),例如正則表示式語法,專門為模式匹配和字串操作問題而設計。也有通用程式語言,例如 C++。
最終,不存在完美的語言。有些語言比其他語言更適合特定型別的問題。每種語言都做出了權衡,在某些領域犧牲效率以換取其他領域的效率。此外,效率不僅意味著執行時效能,還包括開發時間、程式碼可維護性以及影響軟體開發的其他因素。最佳語言取決於程式設計師的具體目標。
此外,在選擇語言時,另一個非常實用的考慮因素是程式設計師可用的該語言的工具的數量和質量。無論理論上語言有多好,如果在目標平臺上沒有可靠的工具集,那麼這種語言就不是最佳選擇。
最優語言(在執行時效能方面)是機器碼,但機器碼(二進位制)是編碼時間效率最低的程式語言。使用高階語言編寫大型系統非常複雜,而使用機器碼則超出了人類的能力。在接下來的部分中,將比較 C++ 與其他密切相關的語言,如C、Java、C#、C++/CLI 和D。
上面的引言表明,目前沒有哪種程式語言能夠直接將概念或想法轉化為有用的程式碼,但有一些解決方案可以幫助解決這個問題。我們將介紹計算機輔助軟體工程 (CASE)工具的使用,這些工具將解決部分問題,但其使用需要規劃,並具有一定的複雜性。
這些部分的意圖不是為了推崇一種語言高於另一種語言;每種語言都有其適用性。有些在特定任務中更勝一籌,有些更易於學習,還有些僅為程式設計師提供更好的控制級別。這一切也可能取決於程式設計師對特定語言的控制級別。
在 C++ 中,垃圾回收是可選的,而不是必需的。在本書的垃圾回收部分,我們將深入探討這個問題。
正如我們將在本書的資源獲取即初始化 (RAII) 部分中看到的那樣,RAII 可以用來為大多數問題提供更好的解決方案。當 finally 用於清理時,它必須在每次使用該類時由類的客戶端編寫(例如,fileClass 類的客戶端必須在 try/catch/finally 塊中進行 I/O,以便能夠保證 fileClass 被關閉)。使用 RAII,fileClass 的解構函式可以保證這一點。現在,清理程式碼只需要編寫一次——在 fileClass 的解構函式中;類的使用者無需做任何事。
預設情況下,C++ 編譯器通常會“修飾”函式的名稱,以便於函式過載和泛型函式。在某些情況下,您需要訪問在 C++ 編譯器中沒有建立的函式。為了實現這一點,您需要使用 extern 關鍵字來宣告該函式為外部函式
extern "C" void LibraryFunction();
C 在 Bjarne Stroustrup 決定建立“更好的 C”時,基本上是 C++ 的核心語言。許多語法約定和規則仍然適用,因此我們可以說 C 是 C++ 的一個子集。大多數最新的 C++ 編譯器也可以編譯 C 程式碼,考慮到一些小的不相容性,因為C99 和 C++ 2003 已經不再相容。您也可以在C 程式設計華夏公益教科書上檢視有關 C 語言的更多資訊。
1998 年由 ANSI 標準定義的 C++(有時稱為 C++98)幾乎是,但並不完全是 1989 年由第一個 ANSI 標準定義的 C 語言(稱為 C89)的超集。C++ 不是嚴格超集的方式有很多,也就是說,並非所有有效的 C89 程式都是有效的 C++ 程式,但將 C 程式碼轉換為有效的 C++ 程式碼的過程相當簡單(避免使用保留字,透過強制轉換繞過更嚴格的 C++ 型別檢查,宣告所有呼叫的函式,等等)。
1999 年,C 語言進行了修訂,並添加了許多新功能。截至 2004 年,這些新功能中的大多數“C99”功能都未包含在 C++ 中。有些人(包括 Stroustrup 本人)認為,C99 帶來的變化具有與 C++98 對 C89 的新增不同的哲學,因此,這些 C99 變化旨在增加 C 和 C++ 之間的相容性。
語言的合併似乎已經成為一個死議題,因為 C 和 C++ 標準委員會之間的協調行動沒有取得實際成果,可以說這兩種語言開始分化。
一些差異是
- C++ 支援函式過載,這在 C 中沒有,特別是在 C89 中(可以爭論,根據函式過載的定義鬆緊程度,在一定程度上可以使用 C99 標準來模擬這些功能)。
- C++ 支援 繼承 和 多型性。
- C++ 添加了關鍵字 class,但保留了 C 中的 struct,具有相容的語義。
- C++ 支援對類成員的訪問控制。
- C++ 透過使用 模板 支援泛型程式設計。
- C++ 使用自己的標準庫擴充套件了 C89 標準庫。
- C++ 和 C99 提供了不同的複數功能。
- C++ 具有 bool 和 wchar_t 作為基本型別,而在 C 中它們是型別定義。
- C++ 比較運算子返回 bool,而 C 返回 int。
- C++ 支援運算子過載。
- C++ 字元常量型別為 char,而 C 字元常量型別為 int。
- C++ 具有特定的 強制轉換運算子 (
static_cast,dynamic_cast,const_cast和reinterpret_cast)。 - C++ 添加了 mutable 關鍵字來解決物理常量和邏輯常量之間不完全匹配的問題。
- C++ 使用引用擴充套件了型別系統。
- C++ 支援使用者定義型別的 成員函式、建構函式 和 解構函式,以建立不變數並管理資源。
- C++ 透過 typeid 和
dynamic_cast支援 執行時型別識別 (RTTI)。 - C++ 包含 異常處理。
- C++ 有std::vector作為其標準庫的一部分,而不是 C 中的可變長度陣列。
- C++ 將
sizeof運算子視為編譯時操作,而 C 允許它成為執行時操作。 - C++ 具有 new 和 delete 運算子,而 C 使用 malloc 和 free 庫函式。
- C++ 支援面向物件程式設計,無需擴充套件。
- C++ 不需要使用宏,與 C 不同,C 使用宏進行謹慎的資訊隱藏和抽象(這對 C 程式碼的可移植性尤為重要)。
- C++ 支援以 // 表示的行註釋。(C99 開始正式支援這種註釋系統,大多數編譯器也支援它作為擴充套件)。
- C++
register關鍵字的語義與 C 的實現不同。
選擇 C 或 C++
[edit | edit source]通常會發現有人推薦使用 C 而不是 C++(反之亦然),或者抱怨這些語言的一些特性。通常情況下,沒有決定性的理由來偏愛一種語言而不是另一種語言。大多數試圖衡量程式設計師生產力與程式語言之間關係的科學研究表明 C 和 C++ 基本上是相同的。對於某些情況,C 可能是更好的選擇,例如核心程式設計,如硬體驅動程式,或關係資料庫,這些情況不適合面向物件程式設計。另一個考慮因素是,C 編譯器更普遍,因此 C 程式可以在更多平臺上執行。雖然這兩種語言仍在不斷發展,但任何新增的新功能仍然保持與舊程式碼的高度相容性,這使得程式設計師可以自由地使用這些新結構。在專案中,根據程式設計師的熟練程度或專案的需求,通常會制定規則來限制語言某些部分的使用(例如 RTTI、異常或內部迴圈中的虛擬函式)。對於新硬體,首先支援低階語言也是很常見的。由於 C 比 C++ 更簡單、更低階,因此更容易檢查和遵守行業指南。C 的另一個好處是,程式設計師更容易進行低階最佳化,儘管大多數 C++ 編譯器可以自動保證幾乎完美的最佳化。
最終,由程式設計師選擇最適合工作的工具。如果可用的程式設計師只知道 C,那麼很難證明選擇 C++ 來完成專案是合理的。即使在相反的情況下,人們可能期望 C++ 程式設計師生成功能 C 程式碼,但所需的思維方式和經驗並不相同。同樣的道理也適用於 C 程式設計師和 ASM。這是由於語言結構和歷史演化之間存在密切的關係。
有人可能認為,使用 C++ 編譯器的 C++ 子集與使用 C 相同,但實際上,根據使用的編譯器,可能會產生略微不同的結果。Java 程式語言 和 C++ 具有許多共同特徵。以下是兩種語言的比較。有關 Java 的更深入瞭解,請參見 Java 程式設計 WikiBook。
Java
[edit | edit source]Java 最初是為了支援 網路計算 在 嵌入式系統 上而建立的。Java 的設計目的是極度 可移植、安全、多執行緒 和 分散式,而這些都不是 C++ 的設計目標。Java 的語法對於 C 程式設計師來說很熟悉,但沒有直接與 C 保持相容性。Java 的設計初衷也是比 C++ 更簡單,但它仍在繼續發展並超越這種簡化。
在 1999 年到 2009 年的十年間,特別是在致力於企業解決方案的程式設計行業中,“基於咖啡”的語言(依賴於 Smalltalk 中熟悉的“虛擬機器”)變得越來越流行。這是一種效能與生產力的權衡,在當時是非常合理的,因為計算能力和對簡化和更簡潔的語言的需求(不僅易於採用,而且學習曲線更低)都很重要。這兩種語言之間有足夠的相似之處,使得熟練的 C++ 程式設計師可以輕鬆地適應 Java,而 Java 在今天仍然比 C++ 更簡單,而且在採用的正規化方面也比 C++ 更一致。
然而,這種興趣的轉變已經減少,這主要歸因於這兩種語言的演變。C++ 和 Java 的演變已經彌合了兩種語言的許多問題和侷限性,如今的軟體需求也更加分散和多樣化。現在,我們對移動裝置、資料中心和桌面計算有特定的需求,這使得程式語言選擇成為一個更加重要的議題。
C++ 和 Java 之間的差異是
- C++ 解析比 Java 稍微複雜一些;例如,
Foo<1>(3);如果 Foo 是一個變數,則是一系列比較,但如果 Foo 是一個類模板的名稱,則會建立一個物件。 - C++ 允許
名稱空間級別的常量、變數和函式。所有這樣的 Java 宣告必須在類或 介面 中。 - C++ 中的
const表示資料為“只讀”,並應用於型別。Java 中的final表示變數不能重新分配。對於基本型別,例如const int與final int,它們是相同的,但對於複雜類,它們是不同的。 - C++ 在 C++11 標準之前不支援建構函式委託,而且只有最近的編譯器才支援這種功能。
- C++ 生成在硬體上執行的機器程式碼,而 Java 生成在虛擬機器上執行的位元組碼,因此使用 C++ 可以獲得更高的效能,但代價是可移植性。
- C++,int main()本身就是一個函式,沒有類。
- C++ 的訪問限定符(public、private)使用標籤和分組完成。
- C++ 預設情況下,對類成員的訪問許可權為private,而在 Java 中則是包訪問許可權。
- C++ 類宣告以分號結尾。
- C++ 缺乏語言級別的垃圾回收支援,而 Java 內建了垃圾回收來處理記憶體釋放。
- C++ 支援
goto語句;Java 不支援,但它的 帶標籤的 break 和 帶標籤的 continue 語句提供了一些結構化的goto功能。事實上,Java 強制實施 結構化控制流,目的是使程式碼更易於理解。 - C++ 提供了一些 Java 缺乏的底層功能。在 C++ 中,可以使用指標來操作特定的記憶體位置,這是編寫底層 作業系統 元件所必需的任務。同樣,許多 C++ 編譯器支援 內聯彙編器。在 Java 中,仍然可以透過 Java 本地介面 將彙編程式碼作為庫訪問。但是,每次呼叫都會產生很大的開銷。
- C++ 允許在原生型別之間進行各種隱式轉換,還允許程式設計師定義涉及複合型別的隱式轉換。但是,Java 只允許原生型別之間的擴充套件轉換是隱式的;任何其他轉換都需要顯式強制轉換語法。C++11 禁止從初始化列表進行收縮轉換。
- 其結果是,雖然 Java 和 C++ 中的迴圈條件(
if、while和for中的退出條件)都期望一個布林表示式,但在 Java 中,諸如if(a = 5)的程式碼會導致編譯錯誤,因為從 int 到 boolean 沒有隱式收縮轉換。如果程式碼是if(a == 5)的拼寫錯誤,這很方便,但當將諸如if (x)的語句從 Java 翻譯到 C++ 時,需要顯式強制轉換會增加冗餘。
- 其結果是,雖然 Java 和 C++ 中的迴圈條件(
- 對於向函式傳遞引數,C++ 支援真正的 傳值呼叫 和 傳引用呼叫。與 C 相似,程式設計師可以使用傳值引數和 間接定址 來模擬按引用傳遞引數。在 Java 中,所有引數都是按值傳遞的,但物件(非基本)引數是 引用 值,這意味著 間接定址 是內建的。
- 通常,Java 內建型別具有指定的大小和範圍;而 C++ 型別具有各種可能的大小、範圍和表示形式,這些表示形式甚至可能在同一編譯器的不同版本之間發生變化,或者可以透過編譯器開關進行配置。
- 特別是,Java 字元是 16 位 Unicode 字元,字串由這些字元的序列組成。C++ 提供窄字元和寬字元,但每種字元的實際大小和使用的字元集都取決於平臺。字串可以由任何一種型別構成。
- C++ 中浮點數的值和運算的舍入和精度取決於平臺。Java 提供了 嚴格浮點模型,該模型保證在不同平臺上得到一致的結果,不過通常使用更寬鬆的操作模式以允許最佳的浮點效能。
- 在 C++ 中,指標 可以直接作為記憶體地址值進行操作。Java 沒有指標——它只有物件引用和陣列引用,它們都不能直接訪問記憶體地址。在 C++ 中,可以構造指向指標的指標,而 Java 引用只能訪問物件。
- 在 C++ 中,指標可以指向函式或成員函式(函式指標 或 函式物件)。Java 中等效的機制使用物件或介面引用。C++11 對函式物件有庫支援。
- C++ 支援程式設計師定義的 運算子過載。Java 中唯一過載的運算子是 "
+" 和 "+=" 運算子,它們既可以連線字串,也可以執行加法。 - Java 特色標準 API 支援 反射 和 動態載入 任意新程式碼。
- Java 有泛型。C++ 有模板。
- Java 和 C++ 都區分原生型別(也稱為“基本”或“內建”型別)和使用者定義型別(也稱為“複合”型別)。在 Java 中,原生型別只有值語義,而複合型別只有引用語義。在 C++ 中,所有型別都有值語義,但可以為任何物件建立引用,這將允許透過引用語義操作該物件。
- C++ 支援任意類的 多重繼承。Java 支援型別的多重繼承,但只支援單一實現繼承。在 Java 中,一個類只能從一個類派生,但一個類可以實現多個 介面。
- Java 明確區分介面和類。在 C++ 中,多重繼承和純虛擬函式使定義與 Java 介面功能相同的類成為可能。
- Java 同時提供語言和標準庫支援 多執行緒。Java 中的
synchronized關鍵字 提供了簡單而安全的 互斥鎖 來支援多執行緒應用程式。C++11 提供了類似的功能。雖然以前版本的 C++ 中的庫提供了互斥鎖機制,但缺乏語言語義使得編寫 執行緒安全 程式碼更加困難且容易出錯。
記憶體管理
[edit | edit source]- Java 需要自動 垃圾回收。C++ 中的記憶體管理通常由人工完成,或透過 智慧指標 完成。C++ 標準允許垃圾回收,但不要求垃圾回收;在實踐中很少使用垃圾回收。當允許重新定位物件時,現代垃圾回收器可以提高整體應用程式的空間和時間效率,而不是使用顯式釋放。
- C++ 可以分配任意大小的記憶體塊。Java 只通過物件例項化分配記憶體。(請注意,在 Java 中,程式設計師可以透過建立位元組陣列來模擬任意記憶體塊的分配。但是,Java 陣列 是物件。)
- Java 和 C++ 使用不同的慣例來管理資源。Java 主要依賴於垃圾回收,而 C++ 主要依賴於 RAII(資源獲取即初始化) 慣例。這反映在兩種語言之間的一些差異。
- 在 C++ 中,通常將複合型別的物件分配為本地棧繫結變數,這些變數在它們 超出範圍 時被銷燬。在 Java 中,複合型別始終在堆上分配,並由垃圾回收器收集(除非在使用 逃逸分析 將堆分配轉換為棧分配的虛擬機器中)。
- C++ 有解構函式,而 Java 有 終結器。兩者都在物件釋放之前呼叫,但它們有很大不同。C++ 物件的解構函式必須隱式(在棧繫結變數的情況下)或顯式呼叫以釋放該物件。解構函式在程式中釋放物件的那一點上 同步 執行。因此,C++ 中的同步、協調的取消初始化和釋放滿足 RAII 慣例。在 Java 中,物件釋放由垃圾回收器隱式處理。Java 物件的終結器在該物件最後一次被訪問之後和實際釋放之前 非同步 呼叫,這可能永遠不會發生。很少有物件需要終結器;只有必須保證在釋放之前清理物件狀態的物件才需要終結器——通常是釋放 JVM 外部資源。在 Java 中,使用 try/finally 結構來執行資源的安全同步釋放。
- 在 C++ 中,可能存在懸垂指標 - 指向已被銷燬物件的引用;嘗試使用懸垂指標通常會導致程式崩潰。在 Java 中,垃圾回收器不會銷燬被引用的物件。
- 在 C++ 中,可能存在已分配但無法訪問的物件。一個無法訪問的物件是指沒有可訪問引用指向它的物件。無法訪問的物件無法被銷燬(釋放),會導致記憶體洩漏。相比之下,在 Java 中,一個物件只有在變得無法訪問(由使用者程式)時,才會被垃圾回收器釋放。(注意:弱引用得到了支援,它與 Java 垃圾回收器協作,允許不同的可訪問性等級。)Java 中的垃圾回收可以防止許多記憶體洩漏,但在某些情況下,記憶體洩漏仍然可能發生。
- C++ 標準庫提供了一組有限的基本且相對通用的元件。Java 擁有一個更大的標準庫。C++ 可以透過(通常是免費的)第三方庫來獲得這些額外的功能,但第三方庫無法提供與標準庫相同的無處不在的跨平臺功能。
- C++ 在很大程度上與 C向後相容,C 庫(例如大多數作業系統的API)可以直接從 C++ 訪問。在 Java 中,其標準庫更豐富,提供對許多功能的跨平臺訪問,這些功能通常只能在特定平臺的庫中使用。從 Java 直接訪問本機作業系統和硬體函式需要使用Java 本機介面。
- C++ 通常直接編譯成機器碼,然後由作業系統直接執行。Java 通常編譯成位元組碼,然後由Java 虛擬機器 (JVM) 進行解釋或JIT 編譯成機器碼並執行。
- 由於 C++ 中某些語言特性使用缺乏約束(例如,未經檢查的陣列訪問、原始指標),程式設計錯誤可能導致低階緩衝區溢位、頁錯誤和段錯誤。標準模板庫提供了更高層次的抽象(如 vector、list 和 map),有助於避免此類錯誤。在 Java 中,此類錯誤要麼根本無法發生,要麼會被JVM 檢測到並以異常的形式報告給應用程式。
- 在 Java 中,對所有陣列訪問操作都隱式地執行邊界檢查。在 C++ 中,對本機陣列的陣列訪問操作不會進行邊界檢查,並且對標準庫集合(如 std::vector 和 std::deque)的隨機訪問元素訪問的邊界檢查是可選的。
- Java 和 C++ 使用不同的技術將程式碼拆分成多個原始檔。Java 使用包系統來規定所有程式定義的檔名和路徑。在 Java 中,編譯器匯入可執行的類檔案。C++ 使用標頭檔案原始碼包含系統,用於在原始檔之間共享宣告。
- C++ 中的模板和宏,包括標準庫中的模板和宏,在編譯後可能導致類似程式碼的重複。其次,與標準庫的動態連結消除了在編譯時繫結庫。
- C++ 編譯包含一個文字化的預處理階段,而 Java 沒有。Java 支援許多最佳化,可以減輕對預處理器的需求,但一些使用者會在其構建過程中新增預處理階段,以便更好地支援條件編譯。
- 在 Java 中,陣列是容器物件,您可以在任何時候檢查其長度。在這兩種語言中,陣列的大小都是固定的。此外,C++ 程式設計師通常只通過指向其第一個元素的指標來引用陣列,而無法從中檢索陣列大小。但是,C++ 和 Java 都提供了容器類(分別為 std::vector 和 java.util.ArrayList),這些容器類是可調整大小的,並存儲其大小。C++11 的 std::array 提供了固定大小的陣列,其效率與傳統陣列類似,具有返回大小的函式,以及可選的邊界檢查。
- Java 的除法和模運算子被定義為截斷為零。C++ 沒有指定這些運算子是截斷為零還是“截斷為負無窮大”。-3/2 在 Java 中始終為 -1,但 C++ 編譯器可能會
返回-1 或 -2,具體取決於平臺。C99 定義的除法方式與 Java 相同。這兩種語言都保證對於所有 a 和 b(b != 0),(a/b)*b + (a%b) == a。C++ 版本有時會更快,因為它可以自由選擇對處理器本機的任何截斷模式。 - Java 中的整數型別大小是定義好的(int 為 32 位,long 為 64 位),而在 C++ 中,整數和指標的大小取決於編譯器。因此,精心編寫的 C++ 程式碼可以利用 64 位處理器的功能,同時仍然可以在 32 位處理器上正常執行。但是,沒有考慮處理器字長編寫的 C++ 程式可能在某些編譯器上無法正常執行。相反,Java 中固定的整數大小意味著程式設計師無需關心不同的整數大小,並且程式會完全相同地執行。這可能會導致效能損失,因為 Java 程式碼無法使用任意處理器的字長執行。C++11 提供了 uint32_t 等型別,其大小得到保證,但編譯器並非強制在沒有對該大小提供本機支援的硬體上提供這些型別。
計算效能是指硬體和軟體系統在執行計算工作(如演算法或事務)時,資源消耗的度量。更高效能被定義為“使用更少的資源”。我們感興趣的資源包括記憶體、頻寬、持久儲存和 CPU 週期。由於現代桌面和伺服器系統上除 CPU 週期外,其他資源都非常充足,因此效能通常被視為指最少的 CPU 週期;這通常直接轉化為最少的時間。比較兩種軟體語言的效能需要一個固定的硬體平臺,以及(通常是相對的)兩種或多種軟體子系統的度量。本節將比較 C++ 和 Java 在 Windows 和 Linux 等常見作業系統上的相對計算效能。
早期的 Java 版本的效能明顯落後於 C++ 等靜態編譯語言。這是因為這兩種密切相關的語言的程式語句可能編譯成少量機器指令(C++),而編譯成更多涉及多個機器指令的位元組碼(由 Java JVM 解釋)。例如
| Java/C++ 語句 | C++ 生成的程式碼 | Java 生成的位元組碼 |
|---|---|---|
| vector[i]++; | mov edx,[ebp+4h] mov eax,[ebp+1Ch] |
aload_1 iload_2 |
雖然這在嵌入式系統中可能仍然是這種情況,因為需要佔用很少的空間,但針對長時間執行的伺服器和桌面 Java 程序的即時 (JIT) 編譯器技術的進步已經縮小了效能差距,並在某些情況下甚至讓 Java 取得了效能優勢。實際上,Java 位元組碼在執行時被編譯成機器指令,其方式類似於 C++ 的靜態編譯,從而產生類似的指令序列。
目前,C++ 在大多數操作中仍然比 Java 快,即使在低階和數值計算中也是如此。有關更深入的資訊,您可以檢視 Java 與 C++ 的效能對比。它有點偏向 Java,但非常詳細。
C 和 C++ 程式設計師可能會對匯入的工作方式感到困惑,反之,例如,Java 程式設計師可能會對 include 檔案的正確使用感到困惑。在比較現代程式語言中的符號表 **匯入** 與 **#includes** 的使用(例如 C 和 C++)時。雖然這兩種技術都是解決同一問題的解決方案,即跨多個原始檔進行編譯,但它們是截然不同的技術。由於幾乎所有現代編譯器都包含本質上相同的編譯階段,因此最大的區別可以解釋為:include 發生在編譯的詞法分析階段,而匯入則在語義分析階段才進行。
匯入的優勢
- 匯入不會重複任何詞法分析工作,這通常會導致大型專案編譯速度更快。
- 匯入不需要將程式碼拆分為單獨的檔案進行宣告/實現。
- 匯入更利於物件程式碼的釋出,而不是原始碼。
- 匯入允許原始檔之間存在迴圈依賴關係。
- 匯入隱式地帶有一種機制,用於解決當多個符號表定義相同符號時發生的符號衝突。
匯入的劣勢
- 當可匯入的模組被修改時,由於沒有定義和實現的分離,所有依賴模組都必須重新編譯,這在大型專案中可能會導致大量的編譯時間。
- 匯入需要一種在物件程式碼中定義符號表的標準機制。這種限制是否真正是一個弱點尚可商榷,因為標準符號表對於許多其他原因是有用的。
- 匯入需要一種在編譯時發現符號表的方法(例如 Java 中的類路徑)。然而,當存在一種標準方法來完成此操作時,這並不一定比指定 include 檔案的位置更復雜。
- 當允許迴圈依賴關係時,幾個相互依賴的原始檔的語義分析可能需要交錯進行。
- 除非語言包含對部分型別的支援,否則使用匯入而不是 include 的語言要求一個類的所有原始碼都位於單個原始檔中。
include 的優勢
- 使用 include,原始檔在語義分析階段沒有相互依賴關係。這意味著在這個階段,每個原始檔都可以作為一個獨立的單元進行編譯。
- 將定義和實現分離到標頭檔案和原始檔中,減少了依賴關係,並允許僅在實現細節發生更改時重新編譯受影響的原始檔,而無需重新編譯其他檔案。
- include 檔案與其他預處理器功能結合使用,允許進行幾乎任意的詞法處理。
- 雖然這種做法並不普遍,但如果語言本身不支援某些現代語言特性(例如 Mixin 和方面),include 可以為這些特性提供基本的支援。
- include 不是底層語言語法的組成部分,而是預處理器語法的組成部分。這有一些缺點(需要學習另一種語言),但也有一些優點。預處理器語法,在某些情況下包括 include 檔案本身,可以在幾種不同的語言之間共享。
include 的劣勢
- include 和必要的預處理器可能需要在編譯的詞法分析階段進行更多遍的處理。
- 在大型專案中多次包含標頭檔案的重複編譯速度可能非常慢。然而,這可以透過使用預編譯標頭檔案來緩解。
- 對於初學者來說,正確使用標頭檔案,尤其是全域性變數的宣告,可能很棘手。
- 由於 include 通常需要在原始碼中指定包含檔案的位置,因此環境變數經常需要提供 include 檔案路徑的一部分。更糟糕的是,這種功能在所有編譯器中都沒有以標準方式支援。
一個比較 C++ 和 Java 的示例 在這裡。
C#
[edit | edit source]C#(發音為“See Sharp”)是一種多用途的計算機程式語言,使用 Microsoft .NET Framework 滿足所有開發需求。
我們已經介紹了 Java。C# 非常相似,它採用 C++ 的基本運算子和風格,但強制程式型別安全,這意味著它在名為 虛擬機器 的受控沙箱中執行程式碼。因此,所有程式碼都必須封裝在物件中,以及其他方面。C# 提供了許多擴充套件,以促進與 微軟 的 Windows、COM 和 Visual Basic 的互動。C# 是一個 ECMA 和 ISO 標準。
C# 是微軟對 (當時由 Sun 開發的) Java 語言的回應,Java 語言開始對企業產生重大影響。在他們試圖將 J++ 推向市場失敗以及與 Sun 的法律糾紛之後,微軟將注意力轉向了託管語言,即使是作為一種保持 Visual Basic 相關性並擁有大量開發人員的方式,因此,隨著 Windows “Longhorn” 專案 (後來成為 Windows Vista) 的宣佈,推動了託管語言的發展及其與 Windows 作業系統的整合,並相信從此以後,“所有新的 Windows API 都將是託管的”。
然而,今天,微軟似乎終於認識到,託管語言,即使考慮 Java 的採用,也缺乏開發作業系統的要求。微軟甚至開始了一個基於 C# 的作業系統來測試前提,但意識到所有主要的軟體專案,即使是 Windows 作業系統附帶的實用程式,也大多是基於 C 或 C++ 的。即使託管程式碼仍然有其地位,C 和 C++ 最終被接受為未來可預見時期內軟體行業的核心語言。在 Windows 中,這被稱為 “C++ renaissance”,這是在營銷機器將開發人員籠罩在黑暗時代之後。
- C# 和 C++ 之間的一些相似之處
- 它們都是 **面向物件** 的語言,這意味著它們使用類、繼承和多型性(儘管語法不同)。這可能被認為是一個區別,因為 C# 被認為是一種純粹的面嚮物件語言,而 C++ 支援各種其他 正規化。
- C# 和 C++ 都是 **編譯** 語言,這意味著原始碼必須轉換為二進位制格式才能執行。
- C# 和 C++ 之間的一些區別
- C++ 編譯成機器程式碼,而 C# 編譯成 中間表示,並在 通用語言執行時 (CLR) 虛擬機器上執行。
- C# 通常不使用指標,而在 C++ 中,指標的使用非常頻繁。C# 僅在不安全模式下允許使用指標。
- C# 主要由 Windows 使用,這並不是最方便的,但 C++ 可以毫無問題地用於任何平臺。
- C++ 可以建立獨立應用程式,而 C# 則不能。
- C# 支援 foreach 迴圈,而 C++ 則不支援。
- C++ 支援多重繼承,但 C# 不支援多重繼承。
- 除了 private、public 和 protected 之外,C# 還有兩個額外的修飾符,分別是 internal 和 protected internal。
- C++ 更常用於應用程式開發,因為它與硬體的直接互動和更好的效能要求,而 C# 程式設計主要用於效能不那麼重要的 Web 和桌面應用程式。
- 與 C++ 相比,C# 的劣勢
- 限制:使用 C#,諸如從類進行多重繼承(C# 實現了一種不同的方法,稱為多重實現,其中一個類可以實現多個介面)、在堆疊上宣告物件、確定性銷燬(允許 RAII)以及允許預設引數作為函式引數(在 C# 版本 < 4.0 中)的功能將不可用。
- 效能(速度和大小):與本機 C++ 相比,使用 C# 構建的應用程式可能效能不佳。C# 具有侵入式垃圾回收器、引用跟蹤以及框架服務中的一些其他開銷。僅 .NET 框架本身就具有很大的執行時佔用空間 (~30 Mb 記憶體),並且需要安裝幾個版本的框架。
- 靈活性:由於依賴 .NET 框架,作業系統級功能(系統級 API)由一組通用的函式緩衝,這將減少一些自由度。
- 執行時重新分發:程式需要與 .NET 框架一起分發(預先 Windows XP 或非 Windows 機器),類似於 Java 語言的問題,並附帶所有正常的升級要求。
- 可移植性:完整的 .NET 框架僅在 Windows 作業系統上可用,但有一些開源版本提供了大多數核心功能,並且也支援 GNU-Linux 作業系統,例如 MONO 和 Portable.NET http://www.gnu.org/software/dotgnu/pnet.html。例如,對於 C# 和 CLI 對 C++ 的擴充套件,存在 ECMA 和 ISO .NET 標準。
- 與 C++ 相比,C# 的優勢
C++ 中有幾個缺點在 C# 中得到了解決。
- 其中一個比較微妙的是使用引用變數作為函式引數。當代碼維護人員檢視 C++ 原始碼時,如果呼叫函式在某個標頭檔案中宣告,則直接程式碼不會提供任何指示,表明函式的引數是作為非 const 引用傳遞的。按引用傳遞的引數可以在呼叫函式後更改,而按值傳遞的引數或作為 const 傳遞的引數則不能更改。不熟悉該函式的維護人員在尋找意外變數值變化的位置時,還需要檢查函式的標頭檔案,以確定該函式是否可能已更改了變數的值。C# 要求在函式呼叫中(除了函式宣告之外)放置 **ref** 關鍵字,從而提示維護人員該值可能被函式更改。
- 另一個是記憶體管理,C# 在虛擬機器中執行,該虛擬機器具有處理記憶體管理的能力,但在 C++ 中,開發人員需要自己處理記憶體。C# 具有垃圾回收器,它會釋放未使用的物件的指標指向的記憶體。
一個比較 C++ 和 C# 的示例可以在 這裡找到。
託管 C++ (C++/CLI)
[edit | edit source]託管 C++ 是對託管擴充套件的 C++ 的簡寫,它是 .NET 框架 的一部分,來自 Microsoft。這種 C++ 語言擴充套件是為了新增諸如自動垃圾收集和堆管理、陣列的自動初始化以及對多維陣列的支援等功能而開發的,從而簡化了在 C++ 中程式設計的所有那些細節,否則這些細節必須由程式設計師來完成。
託管 C++ 不會被編譯成機器碼。相反,它被編譯成 通用中間語言,這是一種面向物件的機器語言,以前被稱為 MSIL。
#include<iostream.h>
#include<math.h>
void main()
{
int choose;
double Area,Length,Width,Radius,Base,Height;
cout<<"circle(1)";
cout<<"Square(2)";
cout<<"Rectangle(3)";
cout<<"Triangle(4)";
cout<<"select 1,2,3,4:";
loop:
cin>>choose;
if(choose=='1')
{
double Radius;
const double pi=3.142;
cout<<"Enter Radius";
cin>>Radius;
Area=pi*pow(Radius,2);
}
else if(choose=='2')
{
double Length;
cout<<"Enter Length:";
cin>>Length;
Area= pow(1,2);
}
else if (choose=='3')
{
double Length,Width;
cout<<"Enter Length:";
cin>>Length;
cout<<"Enter Width:";
cin>>Width;
Area=Length*Width;
}
else if(choose=='4')
{
double Base,Height;
cout<<"Enter Base:";
cin>>Base;
cout<<"Enter Height:";
cin>>Height;
Area=Height*Base/2;
}
else
{
cout<<"Select only 1,2,3,4:";
goto loop;
}
cout<<"Area:"<<Area;
}
D
[edit | edit source]由 Digital Mars(一家小型美國軟體公司,也以生產 C 編譯器(隨著時間的推移被稱為 Datalight C 編譯器、Zorland C 和 Zortech C)而聞名)內部開發的 D 程式語言,第一個用於 Windows 的 C++ 編譯器(最初被稱為 Zortech C++,後更名為 Symantec C++,現在是 Digital Mars C++ (DMC++))以及各種實用程式(例如支援 MFC 庫的用於 Windows 的 IDE)。
該語言最初由 Walter Bright 設計,自 2006 年以來,它一直與 Andrei Alexandrescu 和其他貢獻者合作。雖然 D 起源於 C++ 的重新設計,並且主要受其影響,但 D 並不是 C++ 的變體。D 重新設計了一些 C++ 特性,並受其他程式語言中使用的概念影響,例如 Java、C# 和 Eiffel。因此,D 是一種不斷發展的開源系統程式語言,支援多種程式設計正規化。
它支援過程式、泛型、函式式和麵向物件的正規化。最值得注意的是,它提供了功能強大但易於使用的編譯時超程式設計功能。
它旨在提供效率、控制和建模能力與安全性以及程式設計師生產力的實用組合。它的另一個目標是易於初學者使用,並在有經驗的程式設計師需要時提供高階功能。
支援的平臺
[edit | edit source]D 在 Windows、Linux、OSX 和 FreeBSD 上的 x86 和 x86_64 上得到官方支援。其他平臺(Android、iOS 和 Solaris)和硬體(ARM、MIPS 和 Power-PC)的支援正在開發中。
編譯器
[edit | edit source]有 3 個生產就緒的編譯器:DMD、GDC 和 LDC。
- DMD 是參考實現。另外兩個編譯器共享 DMD 的前端。它提供了非常快的編譯速度,這對開發時間很有用。
- GDC 使用 GCC' 的後端進行程式碼生成。它與 GNU 工具鏈很好地整合。
- LDC 使用 LLVM' 的後端。它可以很好地與 LLVM 工具鏈的其他部分整合。
與 C 和 C++ 的介面
[edit | edit source]D 可以直接連結 C 和 C++ (*) 靜態和共享庫,無需任何包裝器或額外開銷(與 C 和 C++ 相比)。支援 C++ 平臺特定 ABI 的子集(例如 GCC 和 MSVC)
- C++ 命名修飾約定,如名稱空間、函式名和其他
- C++ 函式呼叫約定
- C++ 虛擬函式表佈局,用於單繼承
通常,D 在每個平臺上使用平臺連結器(ld.bfd、ld.gold 等,在 Linux 上),唯一的例外是 Windows,在 Windows 上,預設情況下使用 Optlink。MSVC link.exe 也受支援,但必須首先下載 Windows SDK。
D 中缺少 C 和 C++ 的功能
[edit | edit source]C/C++ 程式設計師會發現的一些新功能是
- 透過內省進行設計——可以設計一個模板類或結構體,以在編譯時檢查其模板引數的不同功能,然後適應這些功能。例如,可組合的分配器設計可以檢查父分配器是否提供重新分配,並有效地委派給它,或者回退到使用 malloc() 和 free() 實現重新分配,或者根本不提供它。這樣做在編譯時的益處是,所述分配器的使用者可以知道他是否應該使用 reallocate(),而不是得到神秘的執行時錯誤。
- 真正的模組
- 宣告和匯入的順序(在 C++ 術語中為
#include)無關緊要。無需預先宣告任何內容。您可以重新排列內容,而不會改變含義。 - 更快的編譯速度——C++ 的編譯模型天生就 慢。此外,像 DMD 這樣的編譯器還有進一步的最佳化。
- 更強大的條件編譯,無需預處理器。
pure函式——無副作用的函式,允許進行內部變異。- 不可變性——保證宣告為不可變的變數可以從多個執行緒安全地訪問(無需鎖定和競爭條件)。
- 契約式設計
- 通用函式呼叫語法 (UFCS)——允許像這樣呼叫自由函式
void copyTo(T)(T[] src, T[] dst):sourceArray.copyTo(destinationArray) - 內建單元測試
- 垃圾收集(可選)
scope控制流語句(在 C++ 中使用ScopeGuard慣用法部分模擬)。
一等公民
- 動態陣列
int[] array; //declare empty array variable
array ~= 42; //append 42 to the array; array.equals([ 42 ]) == true
array.length = 5; //set the length to 5; will reallocate if needed
int[] other = new int[5]; // declare an array of five elements
other[] = 18; // fill the array with 18; other.equals([18, 18, 18, 18, 18]) == true
array[] = array[] * other[]; //array[i] becomes array[i] * other[i]
array[$ - 1] = -273; // set the last element to -273; when indexing an array the $ context variable is translated to array.length
int[] s = array[2 .. $]; // s points to the last 3 elements of array (no copying occurs).
- Unicode 字串
string s1 = "Hello "; // array of immutable UTF8 chars
immutable(char)[] s2 = "World "; // `s2` has the same type as `s1`
string s3 = s1 ~ s2; // set `s3` to point to the result of concatenating `s1` with `s2`
char[] s4 = s3.dup; // `s4` points to the mutable array "Hello World "
s4[$-1] = '!'; // change the last character in the string
s4 ~= "<-> Здравей, свят!"; // append Cyrillic characters that don't fit in a single UTF-8 code-unit
import std.conv : to;
wstring ws = s4.to!wstring; //convert s4 to an array of immutable UTF16 chars
foreach (dchar character; ws) // iterate over ws; 'character' is an automatically transcoded UTF32 code-point
{
import std.stdio : writeln; // scoped selective imports
character.writeln(); //write each character on a new line
}
您可以在 dpaste.dzfl.pl - 專注於 D 的線上編譯器和協作工具 中找到一個可執行的示例。
- 關聯陣列
struct Point { uint x; uint y; } // toHash is automatically generated by the compiler, if not user provided
Point[string] table; // hashtable string -> Data
table["Zero"] = Point(0, 0);
table["BottomRight"] = Point(uint.max, uint.max);
- 巢狀函式
- 閉包(C++11 添加了 lambda 函式,但透過引用捕獲變數的 lambda 函式不允許逃逸建立它們的函式)。
- 內部類
D 中缺少 C++ 功能
[edit | edit source]- 預處理器
- 具有非虛擬解構函式的多型型別
- 多型值型別——在 D 中,
struct是不支援繼承和虛擬函式的值型別,class是支援繼承和虛擬函式的引用型別。 - 多重繼承——D 類僅提供 Java 和 C# 風格的介面多重實現。相反,為了程式碼重用,D 更傾向於組合、
mixin和alias this。
有關更多詳細資訊,請參閱 D 程式設計 書籍。
章節摘要
[edit | edit source]
- ↑ "根據 TIOBE 指數,C++ 是第三大流行的程式語言". 2020年9月.
