深入了解 C++ 與 Java

已發表: 2022-07-22

無數文章比較了 C++ 和 Java 的技術特性,但哪些差異最需要考慮? 例如,當比較顯示 Java 不支持多重繼承而 C++ 支持時,這是什麼意思? 這是一件好事嗎? 一些人認為這是 Java 的一個優勢,而另一些人則認為這是一個問題。

讓我們探討一下開發人員應該選擇 C++、Java 或其他語言的情況,更重要的是,為什麼這個決定很重要。

檢查基礎:語言構建和生態系統

C++ 於 1985 年作為 C 編譯器的前端推出,類似於 TypeScript 編譯為 JavaScript 的方式。 現代 C++ 編譯器通常編譯為本機機器代碼。 儘管有些人聲稱 C++ 的編譯器降低了它的可移植性,並且它們確實需要為新的目標架構重新構建,但 C++ 代碼幾乎可以在每個處理器平台上運行。

Java 於 1995 年首次發布,並不直接構建為本機代碼。 相反,Java 構建字節碼,一種在 Java 虛擬機 (JVM) 上運行的中間二進製表示。 換句話說,Java 編譯器的輸出需要特定於平台的本機可執行文件才能運行。

C++ 和 Java 都屬於類 C 語言,因為它們的語法通常類似於 C。 最顯著的區別在於它們的生態系統:雖然 C++ 可以無縫調用基於 C 或 C++ 或操作系統 API 的庫,但 Java 最適合基於 Java 的庫。 您可以使用 Java 本機接口 (JNI) API 訪問 Java 中的 C 庫,但它容易出錯並且需要一些 C 或 C++ 代碼。 C++ 也比 Java 更容易與硬件交互,因為 C++ 是一種低級語言。

詳細權衡:泛型、內存等

我們可以從多個角度將 C++ 與 Java 進行比較。 在某些情況下,C++ 和 Java 之間的決定是明確的。 除非應用程序是遊戲,否則原生 Android 應用程序通常應該使用 Java。 大多數遊戲開發者應該選擇 C++ 或其他語言以獲得盡可能流暢的實時動畫; Java 的內存管理經常會導致遊戲過程中出現延遲。

非遊戲的跨平台應用程序超出了本次討論的範圍。 在這種情況下,C++ 和 Java 都不是理想的,因為它們對於高效的 GUI 開發來說過於冗長。 對於高性能應用程序,最好創建 C++ 模塊來完成繁重的工作,並為 GUI 使用更具開發人員生產力的語言。

非遊戲的跨平台應用程序超出了本次討論的範圍。 在這種情況下,C++ 和 Java 都不是理想的,因為它們對於高效的 GUI 開發來說過於冗長。

鳴叫

對於某些項目,選擇可能不清楚,所以讓我們進一步比較:

特徵C++ 爪哇
適合初學者是的
運行時性能最好的好的
潛伏可預見不可預料的
引用計數智能指針是的
全局標記和清除垃圾收集必需的
堆棧內存分配是的
編譯為本機可執行文件是的
編譯成 Java 字節碼是的
與低級操作系統 API 直接交互是的需要 C 代碼
與 C 庫的直接交互是的需要 C 代碼
與 Java 庫直接交互通過 JNI 是的
標準化的構建和包管理馬文


除了表中比較的特性外,我們還將關注面向對象編程 (OOP) 特性,如多重繼承、泛型/模板和反射。 請注意,兩種語言都支持 OOP:Java 強制要求它,而 C++ 支持 OOP 以及全局函數和靜態數據。

多重繼承

在 OOP 中,繼承是指子類從父類繼承屬性和方法。 一個標準示例是繼承自更通用的Shape類的Rectangle類:

 // Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };

多重繼承是指子類從多個父類繼承。 下面是一個示例,使用了RectangleShape類以及一個附加的Clickable類:

 // Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };

在這種情況下,我們有兩種基本類型: ShapeRectangle的基本類型)和ClickableClickableRectangle繼承自兩者以組成兩種對像類型。

C++支持多重繼承; Java 沒有。 多重繼承在某些邊緣情況下很有用,例如:

  • 創建高級領域特定語言 (DSL)。
  • 在編譯時執行複雜的計算。
  • 以 Java 中根本不可能的方式提高項目類型的安全性。

但是,通常不鼓勵使用多重繼承。 除非與模板元編程結合使用,否則它會使代碼複雜化並影響性能——最好只有最有經驗的 C++ 程序員才能做到這一點。

泛型和模板

適用於任何數據類型的類的通用版本對於代碼重用是實用的。 兩種語言都提供這種支持——Java 通過泛型,C++ 通過模板——但 C++ 模板的靈活性可以使高級編程更安全、更健壯。 每次您在模板中使用不同類型時,C++ 編譯器都會創建新的自定義類或函數。 此外,C++模板可以根據頂層函數的參數類型調用自定義函數,允許特定數據類型有專門的代碼。 這稱為模板特化。 Java 沒有等效的功能。

相反,當使用泛型時,Java 編譯器通過稱為類型擦除的過程創建沒有類型的通用對象。 Java 在編譯期間執行類型檢查,但程序員不能根據其類型參數修改泛型類或方法的行為。 為了更好地理解這一點,讓我們看一個使用 C++ 模板template<class T1, class T2>的通用std::string format(std::string fmt, T1 item1, T2 item2)函數的快速示例我創建的庫:

 std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);

C++ 將生成format函數為std::string format(std::string fmt, std::string item1, int item2) ,而 Java 將在沒有item1item2的特定stringint對像類型的情況下創建它。 在這種情況下,我們的 C++ 模板知道最後一個傳入參數是一個int ,因此可以在第二個format調用中執行必要的std::to_string轉換。 如果沒有模板,C++ printf語句試圖將數字打印為字符串,就像在第二種format調用中一樣,將具有未定義的行為,並可能使應用程序崩潰或打印垃圾。 Java 函數在第一次format調用中只能將數字視為字符串,而不能直接將其格式化為十六進制整數。 這是一個簡單的例子,但它展示了 C++ 選擇一個專門的模板來處理任意類對象而不修改其類或format函數的能力。 我們可以使用反射而不是泛型在 Java 中正確生成輸出,儘管這種方法可擴展性較差且更容易出錯。

反射

在 Java 中,可以(在運行時)找出結構細節,例如在類或類類型中哪些成員可用。 這個特性被稱為反射,大概是因為它就像舉起一面鏡子對著物體看裡面有什麼。 (更多信息可以在 Oracle 的反射文檔中找到。)

C++ 沒有完全反射,但現代 C++ 提供運行時類型信息 (RTTI)。 RTTI 允許對特定對像類型進行運行時檢測,但它無法訪問對象成員等信息。

內存管理

C++ 和 Java 之間的另一個關鍵區別是內存管理,它有兩種主要方法:手動,開發人員必須手動跟踪和釋放內存; 和自動的,其中軟件跟踪哪些對象仍在使用以回收未使用的內存。 在 Java 中,垃圾收集就是一個例子。

Java 需要垃圾收集內存,提供比手動方法更容易的內存管理,並消除通常會導致安全漏洞的內存釋放錯誤。 C++ 本身不提供自動內存管理,但它支持一種稱為智能指針的垃圾回收形式。 智能指針使用引用計數,如果使用正確,是安全且高性能的。 C++ 還提供了在對象銷毀時清理或釋放資源的析構函數。

雖然 Java 僅提供堆分配,但 C++ 支持堆分配(使用newdelete或較舊的 C malloc函數)和堆棧分配。 堆棧分配比堆分配更快更安全,因為堆棧是線性數據結構,而堆是基於樹的,因此堆棧內存的分配和釋放要簡單得多。

C++ 與堆棧分配相關的另一個優點是一種稱為資源獲取即初始化 (RAII) 的編程技術。 在 RAII 中,引用等資源與其控制對象的生命週期相關聯; 資源將在該對象的生命週期結束時被銷毀。 RAII 是 C++ 智能指針在沒有手動取消引用的情況下工作的方式——在函數頂部引用的智能指針在退出函數時會自動取消引用。 如果這是對智能指針的最後一次引用,則連接的內存也會被釋放。 儘管 Java 提供了類似的模式,但它比 C++ 的 RAII 更尷尬,尤其是當您需要在同一個代碼塊中創建多個資源時。

運行時性能

Java 具有可靠的運行時性能,但 C++ 仍然佔據桂冠,因為手動內存管理比實際應用程序的垃圾收集更快。 儘管由於 JIT 編譯,Java 在某些極端情況下可以勝過 C++,但 C++ 贏得了大多數非平凡的情況。

特別是,與 C++ 減少堆分配的使用相比,Java 的標準內存庫的分配使垃圾收集器過度工作。 但是,Java 仍然相對較快,並且應該可以接受,除非延遲是最重要的問題——例如,在具有實時限制的遊戲或應用程序中。

構建和包管理

Java 在性能上的不足之處在於它的易用性彌補了這一點。 影響開發人員效率的一個組件是構建和包管理——我們如何構建項目並將外部依賴項引入應用程序。 在 Java 中,一個名為 Maven 的工具將這個過程簡化為幾個簡單的步驟,並與 IntelliJ IDEA 等許多 IDE 集成。

然而,在 C++ 中,不存在標準化的包存儲庫。 甚至沒有在應用程序中構建 C++ 代碼的標準化方法:一些開發人員更喜歡 Visual Studio,而另一些開發人員則使用 CMake 或其他自定義工具集。 進一步增加了複雜性,某些商業 C++ 庫是二進制格式的,並且沒有一致的方法將這些庫集成到構建過程中。 此外,構建設置或編譯器版本的變化可能會給二進制庫的工作帶來挑戰。

初學者友好

構建和包管理的摩擦並不是 C++ 遠不如 Java 對初學者友好的唯一原因。 程序員可能難以安全地調試和使用 C++,除非他們熟悉 C、彙編語言或計算機的低級工作。 把 C++ 想像成一個強大的工具:它可以完成很多事情,但如果濫用就會很危險。

Java 的上述內存管理方法也使其比 C++ 更易於訪問。 Java 程序員不必擔心釋放對象內存,因為該語言會自動處理這些問題。

決策時間:C++ 還是 Java?

一個流程圖,左上角有一個深藍色的“開始”氣泡,最終通過一系列帶有深藍色分支的白色決策連接點連接到其下方的七個淺藍色結論框之一,表示“是”和其他選項,淺藍色的樹枝代表“不”。第一個是“跨平台的 GUI 應用程序?” “是”表示結論,“選擇一個跨平台的開發環境並使用它的主要語言”。 “否”指向“原生 Android 應用程序?”從“是”指向第二個問題,“它是遊戲嗎?”在第二個問題中,“否”指向結論“使用 Java(或 Kotlin)”,“是”指向不同的結論“選擇跨平台遊戲引擎並使用其推薦的語言”。來自“原生 Android 應用?”問題,“否”指向“本機 Windows 應用程序?”從“是”指向第二個問題,“它是遊戲嗎?”在第二個問題中,“是”指向結論,“選擇一個跨平台遊戲引擎並使用其推薦的語言”,而“否”指向不同的結論,“選擇一個 Windows GUI 環境並使用它的主要語言(通常是 C++ 或 C#)。”來自“本機 Windows 應用程序?”問題,“否”指向“服務器應用程序?”從哪個“是”指向次要問題“開發人員類型?”從第二個問題來看,“中等技能”決定指向結論“使用 Java(或 C# 或 TypeScript)”,“熟練”決定指向第三個問題“最高優先級?”從第三個問題來看,“開發人員生產力”決定指向結論“使用 Java(或 C# 或 TypeScript)”,而“性能”決定指向不同的結論“使用 C++(或 Rust)”。從“服務器應用程序?”問題,“否”指向第二個問題,“驅動程序開發?”在第二個問題中,“是”指向結論“使用 C++(或 Rust)”,“否”指向第三個問題“物聯網開發?”從第三個問題,“是”指向結論,“使用 C++(或 Rust)”,“否”指向第四個問題,“高速交易?”在第四個問題中,“是”指向結論,“使用 C++(或 Rust)”,“否”指向最後剩下的結論,“詢問熟悉您的目標域的人”。
為各種項目類型選擇最佳語言的擴展指南。

現在我們已經深入探討了 C++ 和 Java 之間的差異,我們回到我們最初的問題:C++ 還是 Java? 即使對這兩種語言有深入的了解,也沒有一刀切的答案。

不熟悉低級編程概念的軟件工程師在將決定限制為 C++ 或 Java 時可能會更好地選擇 Java,但遊戲等實時環境除外。 另一方面,希望擴展視野的開發人員可能會通過選擇 C++ 了解更多信息。

但是,C++ 和 Java 之間的技術差異可能只是決定的一個小因素。 某些類型的產品需要特定的選擇。 如果您仍然不確定,您可以查閱流程圖——但請記住,它最終可能會將您指向第三種語言。