C++程式設計:與Java的比較
Java程式語言和C++有很多共同點。以下是這兩種語言的比較。要更深入地瞭解Java,請參閱Java程式設計華夏公益教科書。
Java最初是為了支援嵌入式系統上的網路計算而建立的。Java的設計目標是極度可移植、安全、多執行緒和分散式,而這些都不是C++的設計目標。Java的語法對C程式設計師來說很熟悉,但沒有保持與C的直接相容性。Java也專門設計得比C++更簡單,但它仍在繼續發展,超越了這種簡化。
在1999年到2009年之間,特別是在專注於企業解決方案的程式設計行業部分,“基於Java”的語言,依靠在Smalltalk中常見的“虛擬機器”,變得越來越突出。這是效能和生產力之間的權衡,在當時計算能力和對簡化和更精簡語言的需求(不僅允許輕鬆採用,而且學習曲線更低)非常合理的情況下,這是有意義的。這兩種語言之間有足夠的相似之處,熟練的C++程式設計師可以輕鬆地適應Java,即使在今天,與C++相比,Java在某些方面也更簡單,甚至在採用的正規化方面也更一致。
然而,這種興趣的轉變已經減少,主要是因為語言的演變。C++和Java的演變很大程度上彌合了兩種語言的問題和侷限性之間的差距,如今的軟體需求也發生了變化,並且更加碎片化。現在,我們對移動、資料中心和桌面計算有特定的需求,這使得程式語言的選擇成為一個更加核心問題。
C++和Java之間的區別是
- C++的解析比Java稍微複雜一些;例如,如果Foo是一個變數,則
Foo<1>(3);是一系列比較,但如果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中的退出條件)都期望布林表示式,但諸如if(a = 5)之類的程式碼將在Java中導致編譯錯誤,因為從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++ 中可以透過庫獲得互斥鎖機制,但缺乏語言語義使得編寫 執行緒安全程式碼更加困難且容易出錯。
- 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++ 語言特性(例如未檢查的陣列訪問、原始指標)的使用缺乏約束,程式設計錯誤可能導致低階的 緩衝區溢位、頁面錯誤和 段錯誤。標準模板庫提供了更高級別的抽象(如向量、列表和對映)來幫助避免此類錯誤。在 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 + (a%b) == a對於所有a和b(b != 0)都成立。C++版本有時會更快,因為它允許選擇處理器本機的任何截斷模式。 - 整數型別的尺寸在Java中是定義好的(int為32位,long為64位),而在C++中,整數和指標的尺寸取決於編譯器。因此,精心編寫的C++程式碼可以利用64位處理器的功能,同時仍然可以在32位處理器上正常執行。但是,在編寫時未考慮處理器字長的C++程式在某些編譯器上可能無法正常執行。相反,Java固定的整數大小意味著程式設計師無需關心不同的整數大小,程式將以完全相同的方式執行。這可能會導致效能損失,因為Java程式碼無法使用任意處理器的字長執行。C++11提供了諸如uint32_t之類的型別,這些型別具有保證的大小,但編譯器並不強制要求在沒有對該大小提供原生支援的硬體上提供它們。
計算效能是衡量硬體和軟體系統執行計算工作(例如演算法或事務)時資源消耗的指標。更高的效能被定義為“使用更少的資源”。感興趣的資源包括記憶體、頻寬、持久儲存和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 |
雖然對於嵌入式系統來說,由於需要佔用空間小,這種情況可能仍然存在,但是即時(JIT)編譯器技術在長期執行的伺服器和桌面Java程序中的進步縮小了效能差距,並在某些情況下使Java獲得了效能優勢。實際上,Java位元組碼在執行時被編譯成機器指令,其方式類似於C++靜態編譯,從而產生類似的指令序列。
目前,即使在低階和數值計算方面,C++在大多數操作中的速度也仍然快於Java。有關詳細資訊,您可以檢視Java與C++的效能比較。它有點偏向Java,但非常詳細。
C和C++程式設計師之間可能會對匯入的工作原理感到困惑,反之,Java程式設計師也可能對包含檔案的正確使用方法感到困惑。在現代程式語言中的符號表匯入與#includes(如C和C++中)的使用之間進行比較。儘管這兩種技術都是解決同一問題的方案,即跨多個原始檔進行編譯,但它們是截然不同的技術。由於幾乎所有現代編譯器都包含基本相同的編譯階段,因此最大的區別可以用以下事實來解釋:包含發生在編譯的詞法分析階段,而匯入則要等到語義分析階段才進行。
匯入的優點
- 匯入不會重複任何詞法分析工作,這通常會導致大型專案的編譯速度更快。
- 匯入不需要將程式碼拆分為單獨的檔案進行宣告/實現。
- 匯入更好地促進了物件程式碼而不是原始碼的分發。
- 匯入允許原始檔之間的迴圈依賴關係。
- 匯入隱式地提供了一種機制,用於在多個符號表定義相同符號時解決符號衝突。
匯入的缺點
- 當可匯入模組發生更改時,由於沒有定義和實現的分離,因此所有依賴模組都必須重新編譯,這在大型專案中可能需要大量的編譯時間。
- 匯入需要一種在物件程式碼中定義符號表的標準機制。這種限制是否真的是一個弱點尚有爭議,因為標準符號表對於許多其他原因都很有用。
- 匯入需要一種在編譯時發現符號表的方法(例如Java中的類路徑)。但是,當存在一種標準方法來執行此操作時,這並不一定比指定包含檔案的位置更復雜。
- 當允許迴圈依賴關係時,可能需要交錯處理多個相互依賴的原始檔的語義分析。
- 除非語言包含對部分型別的支援,否則使用匯入而不是包含的語言要求類的所有原始碼都位於單個原始檔中。
包含的優點
- 使用包含,原始檔在語義分析階段之間沒有相互依賴關係。這意味著在此階段,每個原始檔都可以作為獨立的單元進行編譯。
- 將定義和實現分離到標頭檔案和原始檔中減少了依賴關係,並且在實現細節發生更改時,只需要重新編譯受影響的原始檔,而不需要其他檔案。
- 與其他預處理器功能結合使用的包含檔案允許進行幾乎任意的詞法處理。
- 儘管這種做法並不普遍,但如果語言本身不支援,包含可以為幾個現代語言特性(例如Mixin和方面)提供基本的支援。
- 包含不是底層語言語法的一部分,而是預處理器語法的一部分。這樣做有一些缺點(需要學習另一種語言),但也有一些優點。預處理器語法,在某些情況下包括檔案本身,可以在幾種不同的語言之間共享。
包含的缺點
- 包含和必需的預處理器可能需要在編譯的詞法分析階段進行更多遍處理。
- 在大型專案中重複編譯多次包含的標頭檔案可能非常慢。但是,可以透過使用預編譯標頭檔案來緩解這種情況。
- 對於初學者來說,正確使用標頭檔案,特別是全域性變數的宣告,可能很棘手。
- 由於包含通常需要在原始碼中指定包含檔案的位置,因此通常需要環境變數來提供包含檔案路徑的一部分。更糟糕的是,此功能在所有編譯器之間都沒有以標準方式支援。
一個比較C++和Java的比較示例這裡存在。