這是一個java語言和C++語言之間的比較。
C++和Java語言之間的不同可以追溯到它們各自的傳統,它們有著不同的設計目標。
C++ 被設計成主要用在系統性應用程序設計上的語言,對C語言進行了擴展。對于C語言這個為運行效率設計的過程式程序設計語言, C++ 特別加上了以下這些特性的支持:靜態類型的面向對象程序設計的支持、異常處理、RAII以及泛型。另外它還加上了一個包含泛型容器和算法的C++庫函數。Java 最開始是被設計用來支持網絡計算。它依賴一個虛擬機來保證安全和可移植性。Java 包含一個可擴展的庫用以提供一個完整的的下層平臺的抽象。Java 是一種靜態面向對象語言,它使用的語法類似C++,但與之不兼容。為了使更多的人到使用更易用的語言,它進行了全新的設計。不同的開發目標導致 C++ 和 Java 這兩種語言的不同的規則以及設計上的平衡點不同。 如下列出不同點:
| C++ | Java |
|---|---|
| 除了一些比較少見的情況之外和C語言兼容 | 沒有對任何之前的語言向前兼容。但在語法上受 C/C++ 的影響很大 |
| 一次編寫多處編譯 | 一次編寫多處運行 |
| 允許過程式程序設計和面向對象程序設計 | 必須使用面向對象的程序設計方式 |
| 允許直接調用本地的系統庫 | 要通過JNI調用, 或者 JNA |
| 能直接使用底層系統接口 | 在一個保護模式下的虛擬機中運行 |
| 只提供對象的類型和類型名 | 是反射的, 允許元程序設計和運行時的動態生成代碼 |
| 有多種二進制兼容標準 (例如:微軟和Itanium/GNU) | 一種二進制兼容標準,允許運行時庫的正確性檢查 |
| 可選的自動邊界檢查. (例如: vector 和 string 這兩個容器的 at() 方法) | 一般都有做邊界檢查。HotSpot (java)(Sun 的虛擬機實現) 可以去掉邊界檢查 |
| 支持本地的無符號數學運算 | 不支持本地的無符號數學運算 |
| 對所有的數字類型有標準的范圍限制,但字節長度是跟實現相關的。標準化的類型可以用 typdef 定義 (uint8_t, ..., uintptr_t) | 在所有平臺上對所有的基本類型都有標準的范圍限制和字節長度 |
| 支持指針,引用,傳值調用 | 基本類型總是使用傳值調用。對象以可以為空的參考的方式傳遞(相當于在C++里使用指向 class 或者 struct 參數的指針)。[1] |
| 顯式的存儲器管理,但有第三方的框架可以提供垃圾搜集的支持。支持析構函數。 | 自動垃圾搜集(可以手動觸發)。沒有析構函數的概念,對 finalize() 的使用是不推薦的 |
| 支持類class,結構struct,聯合union,可以在堆棧或者堆里為它們動態分配存儲器 | 只支持類別,只在堆中為對象分配存儲器。Java SE 6在棧為一些對象分配存儲器的使用了逃逸分析的優化方法 |
| 允許顯式的覆蓋(也叫重寫)類型 | 嚴格的類型安全,除了變寬的類型轉換。Java 1.5 開始支持自動類型包裝和解包裝(Autoboxing/Unboxing) |
| C++庫包括:語言支持,診斷工具,常用工具,字符串,本地化,容器,算法,迭代器,數值,輸入/輸出,C庫。Boost庫提供了更多的功能,包括線程和網絡I/O。用戶必須在一大堆(大部分互相不兼容)第三方GUI或者其他功能庫中進行選擇 | 庫在每次 Java 發布新版本的時候都會更新并增強功能。1.6版本支持:本地化,日志系統,容器和迭代器,算法,GUI 程序設計(但沒有用到系統的GUI),圖形,多線程,網絡,平臺安全,自省機制,動態類別加載,阻塞和非阻塞的I/O,對于xml、XSLT、MIDI也提供了相關接口或者支持類別,數據庫,命名服務(例如 LDAP),密碼學,安全服務(例如 Kerberos),打印服務,WEB 服務。SWT 提供了一個系統相關的GUI的抽象 |
| 大部分運算符可以運算符重載 | 運算符的意義一般來說是不可變的,例外是 + 和 += 運算符被字符串重載了 |
| 完全的多重繼承,包括虛擬繼承 | 類別只允許單繼承,需要多繼承的情況要使用接口 |
| 支持編譯期模板 | 泛型被用來達到和C++模板類似的效果,但由于類型消除它們不能在編譯期間從代碼被編譯成字節碼 |
| 支持函數指針,函數對象,lambda(C++11)和接口 | 沒有函數指針機制。替代的概念是接口,Adapter 和 Listener也是被廣泛使用的 |
| 沒有標準的代碼內嵌文檔機制。不過有第三方的軟件(例如 Doxygen) | Javadoc 標準文檔生成系統 |
const 關鍵字用來定義不可改變的常量和成員函數 | final 提供了一個限制版本的 const,等價于 type* const 的對象指針或者 const的基本類型數據。沒有 const 成員函數,也沒有const type* 指針的等價物 |
支持 goto 語句 | 支持循環標簽(label)和語句塊 |
| 源代碼可以寫成平臺無關的(可以被 Windows、BSD、linux、Mac OS X、Solaris 等編譯,不用修改),也可以寫成利用平臺特有的特性。通常被編譯成本地的機器碼 | 被編譯成Java虛擬機的字節碼。和Java平臺相關,但是源代碼一般來說是不依賴操作系統特有的特性的 |
C++ 是一門強大的語言,設計用在系統程序設計方面。Java語言是設計成簡單易用易學習,并有一個強大的跨平臺的庫。Java庫對一個庫來說相當的大。但Java并不會提供所在平臺的所有特性和接口。C++庫簡單健壯,提供容器和關聯數組的支持。[2]
Foo<1>(3); ,如果 Foo 是一個變量,那么它是一個比較的表達式,但如果 Foo 是一個類模板的名字,那么它會創建一個對象.C++允許名字空間級別的常量,變量和函數. 而所有這樣的 Java 聲明必須在一個類或者接口當中.在 C++ 的聲明中,一個類名可以用來聲明一個此類對象的值. Java 里沒辦法做到這點. 在Java里對象不是值. 在 Java 的聲明中,一個類名聲明的是對此類的一個對象的引用. 而在 C++ 里與之等價的做法是用 "*" 來聲明一個指針.在 C++ 里,"."操作符將一個對象作為一個左操作參數來訪問這個對象的成員. 因為對象在 Java 里不是值,所有的對象都通過引用來訪問,剛才的做法在 Java 里是無法實現的. 在 Java 里,"." 操作符是將一個對象的引用作為左操作參數來訪問這個對象的成員.在C++中和這種做法等價的是 "->".| C++ | Java |
|---|---|
class Foo { // 聲明 Foo 類public: int x; // 成員變量 Foo(): x(0) { // Foo 的構造函數Constructor for Foo, } // 初始化 x int bar(int i) { // 成員函數 bar() return 3*i + x; }}; | class Foo { // 定義類 Foo public int x = 0; // 成員變量, // 以及其值的初始化 public Foo() { // Foo的 構造函數 } public int bar(int i) {// 成員方法 bar() return 3*i + x; }} |
Foo a;// 聲明 a 為一個 Foo 類的對象值,// 使用其缺省的構造函數// 如果你想要用其他的構造函數,// 你可以用 "Foo a(args);" | Foo a;// 聲明 a 為一個 Foo 類的對象的引用a = new Foo();// 使用缺省的構造函數初始化// 如果你想要用其他的構造函數,// 你可以用 "Foo a = new Foo(args);" |
Foo b = a;// 拷貝 a 的內容到一個新的 Foo 類的變量 b 當中;// 另一種可以選擇的語法是 "Foo b(a)" | Foo b = a.clone();// 拷貝所有a這個實例的成員到b,當且僅當,// Foo 實現了一個 public 的 clone() 方法,// 并且 clone() 返回一個新的這個對象的拷貝 |
a.x = 5; // 修改 a 對象 | a.x = 5; // 修改 a 對象 |
cout << b.x << endl;// 輸出 0,因為 b 和 a 是兩個對象 | System.out.PRintln(b.x);// 輸出 0,因為 b 和 a 是兩個對象 |
Foo *c;// 聲明 c 為指向一個 Foo 類對象的指針(初始值是// 未定義的;可能指向任何地方) | Foo c;// 聲明 c 為一個指向 Foo 對象的指針// (如果 c 是一個類的成員,那么初始值為空;// 如果 c 是一個局部變量那么你在使用之前必須// 對它進行初始化) |
c = new Foo();// 將 c 綁定為一個新的 Foo 對象的引用 | c = new Foo();// 將 c 綁定為一個新的 Foo 對象的引用 |
Foo *d = c;// 將 d 綁定為和 c 同一個對象的引用 | Foo d = c;// 將 d 綁定為和 c 同一個對象的引用 |
c->x = 5;// 修改 c 指向的對象 | c.x = 5;// 修改 c 指向的對象 |
a.bar(5); // 對 a 調用 Foo::bar()c->bar(5); // 對 *c 調用 Foo::bar() | a.bar(5); // 對 a 調用 Foo.bar()c.bar(5); // 對 c 調用 Foo.bar() |
cout << d->x << endl;// 輸出 5,因為 d 引用的對象和 c 一樣 | System.out.println(d.x);// 輸出 5,因為 d 引用的對象和 c 一樣 |
| C++ | Java |
|---|---|
const Foo *a; // 你不能通過 a 修改 a 指向的對象 | final Foo a; // 你可以通過 a 修改 a 指向的對象 |
a = new Foo(); | a = new Foo(); // 只能在構造函數里 |
a->x = 5;// 非法 | a.x = 5;// 合法, 你仍然可以修改這個對象 |
Foo *const b = new Foo();// 你可以聲明一個 "const" 指針 | final Foo b = new Foo();// 你可以聲明一個 "final" 引用 |
b = new Foo();// 非法, 你不能對它再次綁定 | b = new Foo();// 非法, 你不能對它再次綁定 |
b->x = 5;// 合法,你還是可以修改這個對象 | b.x = 5;// 合法,你還是可以修改這個對象 |
goto 語句; Java 強制結構化流程控制( structured control flow), 依賴break標簽 和 continue標簽 語句來提供類似于 goto 的部分功能. 一些評論者指出這些標簽化的流程控制打破了結構化編程的單退出點的特點.[3]C++ 提供了一些 Java 缺乏的低級特性. 在 C++ 里, 指針可以用來操作特定的內存位置, 這是在寫低級操作系統模塊的時候必須用到的. 類似的, 許多 C++ 編譯期支持內聯匯編,在 Java 里, 這樣的代碼只能放在外來的庫中,而且在調用的時候只能通過JNI來訪問這些外來庫提供的接口.if, while 和 for 里的退出條件)預期的都是一個布爾表達式, 但 if(a = 5) 這樣的代碼在 Java 里會導致編譯錯誤,因為沒有從整型到布爾的隱式變窄轉換. 如果代碼是 if(a == 5) 的輸錯的情況那么是很方便發現這個錯誤的. 而目前的 C++ 編譯器一般來說只會針對這種情況產生一個警告.對于傳參數給函數的情況, C++ 支持引用傳遞和值傳遞. 在 Java 里, 參數總是值傳遞的.[4] 但在 Java 里,所有的非基本類型的值都只是對于對象的引用 (用 C++ 的術語來說, 它們是智能指針). 對象在 Java 里不是作為值直接被使用的,只有對象的引用可以被直接操作; 習慣于將對象當做值直接使用的 C++ 開發者經常會把這個跟引用傳遞搞混.Java 內建的類型在字節寬度和取值范圍上是被虛擬機定義好的; 在 C++ 里, 內建的類型有定義一個最小取值范圍, 但是其他的部分(字節寬度)可以被映射成具體平臺上支持的原生類型.舉個例子, Java 字符是16位的Unicode字符, 字符串是由這樣的字符組成的序列. C++ 提供窄和寬兩種字符,但實際的字符寬度是和平臺相關的, 視所用的字符集而定. 字符串可以由這兩種字符中的一種組成.浮點數及其操作的精度和舍入方式在 C++ 里是平臺相關的. Java 提供了一個可選的嚴格的浮點數模型,保證跨平臺的一致性,不過可能會導致運行時效率比較差.在 C++ 里, 指針可以作為內存地址直接操作. Java 沒有指針 — 它只有對象引用和數組引用,這兩者都不允許直接用來訪問內存地址. 在 C++ 里可以構造一個指向指針的指針,而 Java 的引用只能指向對象.在 C++ 里, 指針可以指向函數或者方法(函數指針). 在 Java 里的等價物是對象或者接口的引用.雖然有使用棧內存分配的對象, C++ 還是支持區域資源管理, 一個用來自動管理內存和其他系統資源的技術,此技術支持確定性對象銷毀(deterministic object destruction). 不過,區域資源管理在 C++ 里是不被保證的;它只是一個設計模式,所以需要依賴程序員遵守相關的規則. Java 通過使用垃圾搜集來支持自動內存管理,但對于其他的系統資源(窗口,通訊端口,線程),如果垃圾搜集器無法決定它們是否不再被用到,那通常還是需要顯式的釋放的.C++ 的用戶可自定義操作符重載的特性在 Java 里是不支持的. 唯一在 Java 里可以重載的操作符是 "+" 和 "+=" 操作符, 在字符串里重載為連接字符串.Java 的標準應用程序接口支持反射和動態加載任意代碼.C++ 支持靜態和動態的庫連接.Java 支持泛型, 其主要目的是提供類型安全的容器. C++ 支持模板, 在泛型編程方面提供了更強的支持.Java 和 C++ 都對基本類型(也叫"內建"類型)和用戶自定義類型(也叫"復合"類型). 在 Java 里, 基本類型只有值的語義,復合類型只有引用的語義. 在 C++ 里所有的值都有值語義,可以創建對于任何類型的引用,這樣就允許通過引用語義來操作對象.C++ 支持任意類型的多重繼承. 在 Java 里一個類只能從單個的類繼承而來,但一個類可以實現多個的接口(換句話說,它支持類型的多重繼承,但對于實現只能單繼承(it supports multiple inheritance of types, but only single inheritance of implementation))。Java 對于類和接口是顯式區分的. 在 C++ 里多重繼承和純虛函數使得定義出類似于 Java 的接口的類是可能的,不過會有少許區別.Java 在語言和標準庫都對多線程有良好的支持. synchronized 這個 Java 的關鍵字為了支持多線程應用提供了簡單而安全的互斥鎖 ,但同步(synchronized)區只能用 LIFO 的順序離開. Java 也為更高階的多線程同步提供了健壯而復雜的庫. 在 C++ 里沒有專門為多線程定義的內存模型; 但第三方庫提供了和 Java 差不多的功能; 不過這些 C++ 庫之間差異較大,一致性不好.C++ 方法可以聲明為虛函數, 虛函數是在運行期根據對象的類型才確定的. C++ 方法缺省情況下不是虛的. 在 Java 里, 方法缺省情況下是虛的, 但可以使用final關鍵字使之聲明為非虛的.C++ 枚舉屬于基本類型,支持和其他整數類型之間的轉換和比較. Java 枚舉實際上是類的實例(它們從 java.lang.Enum<E> 擴展而來),象其他的類一樣可以定義構造函數,數據成員及方法.C++ 和 Java 都提供泛型編程的能力,分別是模板 和 泛型(Generics in Java). 雖然它們被創造用來解決類似的問題,有類似的語法,但實際上很不一樣.
| C++ 模板 | Java 泛型 |
|---|---|
| 類和函數都可以使用模板. | 類和方法都可以使用泛型. |
| 參數可以是任意類型或者整型. | 參數只能是能被引用的類型(非基本類型). |
| 在編譯的時候對于每種類型生成類或者函數的拷貝. | 對于所有類型的參數,只有一個版本的類或者函數生成. |
| 同一個類用不同類型生成的對象在運行期也是不同類型的 | 編譯完成以后類型參數的類型是被消除的; 同一個類用不同類型參數生成的對象在運行期是相同類型的. |
| 想要用到模板類或者函數的實現代碼的話必須 include 它(只是聲明是不夠的). | 有一個編譯好的類文件里的類或者函數的簽名就足以使用泛型了 |
| 模板可以被具體化 -- 可以為某個特定的模板參數提供單獨的實現. | 泛型不能被具體化. |
| 模板參數可以有缺省參數(default argument)(只針對對于模板類,模板函數是沒有此特性的). | 泛型類參數無法擁有缺省參數. |
| 不支持通配符. 返回的類型經常是嵌套的 typedef 形式的. | 如果只用一次,那么支持通配符作為類型參數. |
| 不直接支持設置類型參數的邊界 (即, 不允許說明類型參數必須為某個類型的子類/父類), 但超編程提供了這個特性[6] | 支持類型參數邊界, 分別以 "extends" 和 "super" 來定義上界和下界; 同時允許定義類型參數之間的繼承關系 |
| 允許生成有參模板的類的實例 (如 foo = new Foo<T>, T 為參數) | 不允許生成有參模板類的實例 (除非使用反射) |
| 泛型類的類型參數無法用在 static 方法和變量上. | |
| static 變量不在在不同的類型參數生成的類之間共享. | static 變量在不同類型參數生成的類的對象之間是共享的. |
| 泛型類和函數在聲明時不強制類參數的類限制. 使用錯誤的類參數會導致模板代碼"不工作". 值得注意的是, 如果使用了錯誤的參數, 則錯誤信息將出現在定義模板的代碼處 (而非調用模板的代碼處), 說明 "不支持以該類型作為參數來實例化模板". 這種錯誤信息往往難以幫助人們找出真正的問題所在 (編程時究竟使用了何種 "錯誤的" 參數). 因此, 模板類或者函數的正確使用更依賴于正確的文檔. 超編程以額外的代價提供了這些特性. | 泛型類和函數在聲明的時候強制了類參數的類限制(Generic classes and functions can enforce type relationships for type parameters in their declaration). 使用一個錯誤的參數會在使用它的時候導致一個類錯誤. 在泛型代碼里操作和參數化類型只能按聲明的時候保證安全的方式來使用. 這用失去彈性的代價來換取好得多的類型方面的安全性. |
| 模板是圖靈完全的 (參見 模板超編程). | 泛型不是圖靈完全的. |
(a/b)*b + (a%b) == a. C++ 版本有時候會更快,因為它允許直接使用處理器的截斷方式.整型的長度在 Java 里是已定義好的(int 為 32-bit, long 為 64-bit), 而在 C++ 里整型和指針的長度是和編譯器以及應用二進制接口相關的. 因此仔細編寫的 C++ 代碼可以利用64位處理器的能力而又可以在32位處理器上工作. 但是需要很仔細的用可移植的方式編寫. 作為對比, Java 的固定整型大小使得程序員無法做到這樣,沒辦法利用處理器的字長會導致 Java 在64位處理器上表現較差.想運行一個編譯好的 Java 程序,計算機上要運行JVM;而編譯好的 C++ 程序不需要額外的應用。比較早期的 Java 版本在性能上比靜態編譯的語言如 C++ 差得很多,這是因為用 C++ 是直接編譯成一些機器指令,而當 Java 編譯成字節碼以后用 JVM 解釋執行的時候又牽涉了不少額外的機器指令。 例如:
| Java/C++ 語句 | C++ 生成的代碼 (x86) | Java 生成的字節碼 |
|---|---|---|
| vector[i]++; | mov edx,[ebp+4h] mov eax,[ebp+1Ch]inc dWord ptr [edx+eax*4] | aload_1 iload_2dup2ialoadiconst_1iaddiastore |
C++ 在大部分的情況下都比 Java 要快,[7] 有幾個數值方面的基準測試的研究爭辯說 Java 在某些情況下可能會比 C++ 的性能好得多。[8][9][10] 但有人說數值方面的基準測試對于語言的評估是不合適的,因為編譯器都可以做相關的優化,甚至可能將被測試的代碼徹底刪除。[11][12][13] 如果涉及到一個真正現實應用的程序,Java 會因為很多原因導致性能變差:[14][15][16]
所有的對象都在堆里被申請。對于使用小對象的函數來說會導致很大的性能損失,因為在棧里申請內存幾乎沒有性能損失。方法缺省是虛的。這對于小對象來說會因為虛表增加好幾倍的內存使用。它也會引起性能損失,因為 JIT 編譯器不得不對查虛表的過程做額外的優化。即使使用標準的容器依然會有很多的類型轉換,這會引起性能損失,因為需要遍歷整個繼承樹。虛擬機更進一步增加了內存的使用,因此降低了內存的局部性,增加了緩存命中失敗率,從而導致整個程序變慢。缺乏低級細節的操作方式使得開發者無法將程序進一步優化,因為編譯器不支持。[17]有人爭論說,和 Java 相比 C++也有很多劣勢:
指針使得優化變得困難,因為它們可能指向任意的數據。當然現在這一點也并非完全正確,因為一些現代的編譯器引入了 "嚴格別名" 的規則 [18] 并且支持 C99 的關鍵字 restrict,從而嚴格限制了指針的使用,使其只能用于指向已知的變量 [19]Java 的垃圾搜集和使用malloc/new來申請內存相比能擁有更好的緩存連貫性,因為它的申請一般來說是順序的。然而,始終有爭論認為二者同樣會導致內存的“零碎化”(即多次分配和回收之后內存空間會變得不連續),且并沒有哪一個比對方有更明顯的緩存優勢。運行時編譯可能可以更好的優化代碼,因為可以利用運行時的額外的信息,例如知道代碼是在什么樣的處理器上運行。然而當今的情況也并非完全如此,因為目前最先進的 C++ 編譯器也會針對不同系統生成不同的目標代碼,以期充分利用該系統的計算能力 [20]此外,有爭議的是,花在更復雜的 C++ 代碼上的 debug 時間太多,用 Java 開發完全可以把這些時間用來優化 Java 代碼。當然對于一個給定的程序來說兩種語言能優化到什么程度也是一方面。最后,對于處理器負擔很重的情況,例如視頻渲染,C++ 能直接訪問硬件,在同樣一個硬件規格下 C++ 總是會比 Java 的表現好很多。
C++不是任何一個公司或者組織的商標,不被任何個人擁有。[21] Java原是Sun的商標,現在由甲骨文公司擁有。[22]
C++語言由 ISO/IEC 14882 定義,是一個ISO標準,由 ISO/IEC JTC1/SC22/WG21 委員會發布。 Java語言由 Java Language Specification 定義,這是一本Sun公司(已被甲骨文收購)出版的書。[23]
轉自:https://zh.wikipedia.org/wiki/Java%E5%92%8CC%2B%2B%E7%9A%84%E5%B0%8D%E7%85%A7
新聞熱點
疑難解答
圖片精選