用 C++ 和 Java 寫算法,有差別嗎?

2021-02-20 GitChat精品課

我寫了七、八年的 「算法博客」,出版了一本《算法的樂趣》,一門《算法應該怎麼「玩」?》課程,所有介紹算法的例子都是用 C++ 編寫的。

很多讀者來向我吐槽:「好好的一本算法書,為什麼要用 C++?」 或者 「C++ 很強大,Java 也很優秀,我選 Python」。

所以在本文裡,我非常詳細的講述了用 Java 或 C++ 寫算法時候的優劣勢,你可以參考一下來判斷自己喜歡用哪種語言寫算法

PS:

下文中,上面的代碼是 C++ 的實現方式,下面的是 Java 的實現方式。

C++ 語法使用的是 C++11之後的版本,Java 使用的是 Java6 之後的版本。

1、C++ 和 Java 語法特性的相似性

同為 C 語言家族的 Java 和 C++ 語言層面的相似性是有客觀基礎的。我通常這樣理解:Java 是跨平臺的 C++,是一種更好的 C++(是不是有點拉仇恨的感覺)。

2、基本數據類型

C++ 的基本數據類型有:int、unsigned int、long、unsigned long、short、unsigned short、char、unsigned char、bool、float 和 double;

相應的,Java 也有 8 種基本數據類型,分別是:byte、short、int、long、float、double、char 和 boolean。

大部分情況下,兩種語言的基本數據類型可以根據下表進行一對一的轉換,但是也有差異,需要特別注意。

首先是 char,C++ 的 char 是 8 比特無符號整數,順便表示了 ASCII 字符;Java 的 char 是 16 比特,天生就可以表示寬字符集的字符。

另一個需要注意的是 long 類型,C++ 的 long 是不可移植類型,在不同的系統上其長度不一樣,可能是 32 位,也可能是 64 位,所以 C++ 程式設計師應儘量避免使用 long。

Java 的 long 比較單純,無論是 32 位的系統還是 64 位的系統,它都表示 64 位整數。

反過來,Java 會用 d 或 D 表示一個直接數字是 double 類型的浮點數,比如 200.0d 或(200.0D),但是 C++ 不支持,因為 C++ 默認一個浮點型的直接數字就是 double 類型。

C++ 用字面量符號 f 或 F 表示一個直接數字是 float 類型浮點數,比如 250.1 f(或 250.1F),這一點 Java 也是一樣的。

C++ 用字面量符號 l 或 L 表示 long,用 UL 表示 unsigned long。

3、字符串

很多 C++ 程式設計師喜歡的用 char* 或 char 類型的數組存儲字符串,這其實是 C 語言用戶帶過來的習慣,我給出的 C++ 算法實現對字符串一般都用 std::string,對應 Java 的 String。

std::string 和 String 的用法對照如下表所示:

4、基本語法

雖然 Java 的語法和 C++ 十分地相似,但是語言層面還有一些不同。C++ 允許全局函數的存在,Java 則不允許,不過 Java 也留了個口子,就是用靜態成員函數。Java 沒有指針,對象的傳遞和返回都是用的引用的方式,並且不需要像 C++ 那樣用 「&」 做特殊的語法標記。

大多數介紹 Java 的書籍開篇就是類和抽象,然後才是基本的語法,這和 Java 上等人的氣質是一致的,連這都不會,咋做程式設計師?C++ 應該多提升一下氣質,少用點指針和全局函數。本文為了對比 C++ 和 Java 的相似性,所以就從基本語法結構開始介紹。

運算符和賦值

二者的運算符幾乎一樣,甚至 「++」 和 「—」 運算符都一樣有前綴式和後綴式兩種形式,意義也一樣;運算符的優先級規則也是一樣的。賦值語句兩者基本上是一樣的,看看每一行結尾的 「;」 你就知道它們有多相似。

條件判斷與循環

條件判斷方面,C++ 與 Java 的 if 語句、switch 語句用法都相同;邏輯表達式的結構和語法、邏輯運算符的優先級也都相同。

C++ 的三種基本循環方式是 while 循環、do…while 循環和 for 循環,Java 都支持,甚至連關鍵字和 break、continue 控制語句的意義也一樣。

C++11 版本引入了一種根據範圍循環的語法,一般理解和 Java 的增強 for 循環類似,比如這種 C++ 循環形式:

Java 與之對應的形式是:

C++ 的基於範圍的 for 循環也可用於 C++ 的標準庫對象,用於取代老舊的迭代器循環方式:

同樣,Java 的增強 for 循環也支持基於 Collection 的遍歷,理解起來不成問題:

傳統的 C++ 語言是用迭代器對標準庫的容器進行遍歷,比如:

C++ 的容器都有 begin() 和 end() 接口,分別得到起始位置的迭代器的值和結束位置的迭代器的值,很多標準庫的算法都會用到迭代器。

C++ 用當前迭代器的值是否等於 end() 代表的結束位置迭代器的值來判斷是否遍歷結束。

Java 的 Collection 也有迭代器的機制,Java 用 hasNext() 判斷是否遍歷結束。

C++ 直接用 「 * 」 提領迭代器,得到對象本身的引用,Java 用迭代器的 next() 接口得到對象本身的引用。以上 C++ 代碼可以翻譯成如下 Java 代碼:

除了以上的 for 循環語句,C++ 還支持 for_each() 形式的遍歷 + 處理操作,也是配合迭代器使用,for_each() 的前兩個參數是一對迭代器,代表循環的起始位置和結束位置。

第三個參數是一個可調用對象,即函數對象(C++11 版本之後,這個參數還可以是一個 Lambda 表達式),舉個慄子:

Java 沒有與之對應的泛型函數接口,但是 Java 的很多 Collection 都支持 forEach() 接口:

C++ 的 for_each()其實用起來並不好用,自從 C++11 之後,除了懷舊派 C++ 程式設計師,其他人應該很少會再用 for_each() 了,基於範圍的 for 循環簡直絲滑的不要不要的。

5、函數

C++ 的函數結構和 Java 也一樣,函數調用的形參和實參對應方式也一樣,也無需多做說明。

6、數組

C++ 和 Java 都支持原生數組,並且數組索引都是從 0 開始。C++ 中定義數組的同時就分配了存儲空間,所以在定義時要指定長度,使用 new 動態申請內存時,要指定長度。

但是一種情況除外,那就是靜態初始化數組的形式,因為此時編譯器知道需要多少空間存儲這些數據,如下是 C++ 定義數組的常用形式:

Java 如果僅僅是聲明一個數組,可以不指定長度,因為此時並不分配存儲空間,需要分配空間的時候,用 new。與之對應的 Java 語言的形式是:

C++ 中二維數組的每一維長度必須相同,因為 C++ 的二維數組實際上只是一塊連續的存儲空間而已,甚至可以用一維數組的下標遍歷全部二維數組的存儲空間。

Java 沒這要求,因為 Java 的每一維都是可以單獨申請存儲空間的。但是二者在使用形式上是一樣的。C++ 定義和初始化二維數組一般有這幾種形式:

與之對應的 Java 語言初始化二維數組的形式是:

C++ 也支持動態內存形式的二維數組,一般有兩種使用方法,Java 都有與之對應的習慣用法:

與之對應的 Java 的方法是:

這代碼相似度很高。C++ 代碼最後要用 delete[] 手動釋放為數組申請的內存,Java 是不需要的。

C++ 還可以利用二維數組在內存中是連續存儲這一特性,使用時用下標計算將一維數組當成二維數組使用,計算的方法是:a\[i]\[j] = b[i * 2 + j],如下代碼示例:

遇到這樣的代碼,需要根據上述對應關係,小心地理解算法代碼的意圖。一些棋盤類遊戲通常喜歡用一維數組存儲二維的邏輯棋盤結構,好在 Java 也可以這麼做,轉換起來也沒什麼難度。

7、枚舉

與 C 相比,C++ 強化了類型差異,枚舉變量和整數變量之間不能互相賦值,但是使用方法依然是直接使用枚舉值,沒有限制域。

C++11 之後,開始支持強類型枚舉,這一點就和 Java 很像了,越來越像一家人了:


8、I/O 系統

C++ 代碼中一般用 std::cin 和 std::cout 進行控制臺的輸入和輸出。也有一些半吊子 C++ 程式設計師會在 C++ 代碼中混用 C 語言的 printf() 列印輸出信息。

不過話說回來,很多語言都支持 printf 方式的格式化輸出,比如 Java、 Python,為啥 C++ 就不能提供一個呢?比如以下代碼接受用戶輸入一個字符串和一個整數,並將其輸出出來:

將其翻譯成 Java,是這個樣子的:

上述代碼示例中,C++ 和 Java 的輸入分隔符都是空格或回車,如果希望輸入帶空格的一整行內容怎麼辦?

C++ 提供了 getline() 函數,getline() 會從緩衝區中取輸入流,直到遇到結束符。

結束符默認是 '\n',實際上是 getline() 函數有三個參數,第三個參數可指定結束符:

Java 也有與之對應的 Buffer IO 方式,請看:

C++ 程式設計師有時候也會用 std::cin::get() 函數,這個函數也是從緩衝區中讀入一行,直到遇到結束符,和 getline() 函數一樣,這個函數也可以指定結束符,如果不指定,默認是'\n'。

但是 std::cin::get() 函數有個小個性,就是它不從緩衝區中讀出結束符,而是將結束符留在緩衝區中。

為了適應它的這個小個性,C++ 程式設計師通常會在後面跟一個 get,將結束符讀出並丟棄掉,所以代碼看起來有點怪怪的:

理解了這一點,看懂 C++ 代碼也就不難了。當然,無論是 C++ 還是 Java,其 I/O 系統都非常複雜,有流式 I/O,也有緩衝區 I/O,操作的數據可以是控制臺 I/O,也可以是文件 I/O。

9、類和封裝

首先說說 C++ 的 struct,Java 沒有與之對應的相似物的,但是完全可以用 class 來替換這個概念。為什麼這麼說呢?

因為在 C++ 中,struct 的位置有點尷尬,它是個 POD 吧,但它的成員又可以用非 POD 的數據類型,比如 std::string,甚至還可以定義虛接口,一旦有了這些東西,它就算不上 POD 了,除了成員默認是公有之外,和 class 沒有太大差別。

在我看來,C++ 保留 struct 的主要意義是為了兼容舊的 C 或 C++ 的庫,這些庫中很多接口用到了 struct,其次是純粹作為一種 POD 的 value type 來使用。

我的算法代碼中也會用到 struct,大概是為了懷舊吧,其實完全可以用 class 代替,當然也可以很容易地翻譯成 Java 的 class。來看個例子,對於這個 struct:

可以很輕鬆的轉成 class:

自從 C++11 發布以後,我就覺得 C++ 和 Java 的 class 越來越像了,分開這麼多年後,C++ 終於也支持 final 和 override 了。

從語法層面看,二者的差異很小,就小規模的算法而言,也很少會用到繼承和重載之類的情況,所以,Java 程式設計師看懂 C++ 的 class 定義與實現一點都不難。

少有的一些差異,比如 C++ 的函數可以設置參數默認值,或者 C++ 的抽象機制採用的虛函數要使用 virtual 關鍵字等。先看一個典型的 C++ 類的定義與實現:

C++ 的類成員訪問控制採用分節控制,用 public: 或 protected: 作為分節的標誌,如果沒有分節標誌的類成員,則是默認的 private: 控制。

C++ 的成員函數可以有默認值,並且構造函數也支持默認值。以 Bucket 類為例,構造函數第二個參數默認值是 0,即在構造 Bucket 對象時,可以只確定一個參數 capicity,也可以在確定 capicity 參數的同時,確定 Bucket 的水量,比如:

Java 不支持參數默認值,但是可以通過重載函數解決這個問題,即增加一個只有 capicity 參數的構造函數:

C++ 沒有抽象基類的語法,但是又抽象基類的概念,一般當一個類中有一個純虛函數的時候,這個類是不能被直接實例化的,它就類似於是一個抽象基類,比如:

C++ 的函數有很多類型修飾,比如常見的 const,C++11 後新增了 final 和 override,但是 = 0 一直是一個比較奇怪的存在,它表明這個函數沒有實現,需要在派生類中實現,同時,也說明這個類是不能被實例化的。

對於這樣的機制,Java 可以理解為這就是個抽象基類:

C++ 的繼承體系的語法與 Java 類似,只是語法形式上不同,Java 採用關鍵字:extends。

C++ 對於基類聲明的虛函數,繼承類中不需要再用 virtual 關鍵字修飾,當然,加了 virtual 關鍵字也沒錯誤。Java 也一樣,abstract 關鍵字再繼承類中可以省去。

從 Shape 抽象類派生一個 Circle 類,C++ 的典型代碼是:

Circle 構造函數後面的 :Shape(color),表示對基類的初始化,對於 Java 語言來說,相當於調用 super(color)。

以上代碼翻譯成 Java 語言,應該是這樣的形式:

C++ 有時候也會將一個類聲明為 final,意味著它不希望被其他類繼承,從語法上做了限制,比如:

有時候,是某個不希望被派生類重載,比如:

這些對於 Java 程式設計師來說,並不陌生,語法上只是 final 關鍵字的位置不同,理解上應該不存在任何問題。

10、總結

本文介紹了 C++ 和 Java 在基本語法層面的對應關係,因為算法代碼涉及的語言方面深度有限,所以本文介紹的內容也比較基礎。

通過對比發現不管是用 C++ 還是用 Java 來寫算法,差別基本不大,如果朋友們對算法想再深度了解,可以看一下《算法應該怎麼「玩」?》

▼掃碼跟我一起學

這門課選擇了 30 多個簡單且實用的算法實例,基本覆蓋了各種算法比賽中常見的題目以及生活中一些有趣的算法實現。側重通過多個例子,來引導大家掌握常見的算法設計思想。

你會用哪種語言來寫算法呢?在評論區告訴我吧~對算法感興趣的同學,點擊 閱讀原文,試讀了解。

相關焦點

  • C++、java 和 C 的區別
    一、基礎類型c++:** java:** C#:1.以java為準,c++裡面的int short long 像這樣的整型 一般都有unsigned 和signed的區分 ,這個跟java和c# 的區別比較大,但c#裡面有unit ulong ushort 這三種就相當於c++的修飾詞unsigned,當c++李明的變量類型定義unsigned,就默認是整數。
  • 你知道計算機語言、編程、算法及軟體開發之間有什麼聯繫和區別嗎
    總結起來,有很多問題可以歸類於一種問題,那就是有很多初學者,或者剛剛進入到計算機編程領域、軟體開發新手對一些概念還是搞不清楚,不能夠正確理解計算機語言、計算機編程、計算機算法及軟體開發之間的聯繫和區別。我們可以說計算機語言、計算機編程、計算機算法及軟體開發都屬於軟體範疇,最終的目的是開發出一個(套、種)計算機軟體,達到某些功能從而滿足人們的一定需求。
  • 程式設計師吐槽女博士:一個搞算法的,竟然問tab和空格混用怎麼解決
    程式設計師吐槽女博士:一個搞算法的,竟然問tab和空格混用怎麼解決!隨著現在網際網路公司的要求越來越高,很多公司在面試的時候都會要求程式設計師會一點算法知識。一個好的算法不僅是編寫程序的模型,更是能保障程序正確執行又能提高效率不可或缺的一部分,同時學習算法的還能提高自己的編程能力。近日,一位程式設計師吐糟同組搞算法的女博士,一直問一些弱智的問題,比如,怎麼知道我用的是python2還是python3?tab和空格混用怎麼解決?print家括號是python2的要求?c++怎麼釋放new申請的動態數組?
  • c++ fstream + string 處理大數據
    /FileWriter,BufferedReader/BufferedWriter等類,詳見java讀寫文件(2)應用java的原因是java裡面的map非常靈活,eclipse編譯器更是給力,而且ctrl可以追蹤函數等,詳見java map的排序(3)應用java的另一個原因是java裡面的string類的字符串處理非常靈活,各種函數是應用盡有。
  • 三位斬獲百度C++後臺開發offer大佬的口述分享!!!
    你看,這個項目是java的,那麼你應聘c++,那人家多半會問,為什麼項目是java的,那就可以說明是和導師要求的整體項目走的,記住強調你熟悉c++,這個時候說「以前本科一直使用c++,和導師要適應這種變化,所以就學習java,可以側重說自己認為學習語言是有通性的(隱藏誘導題目),而且體現自己接受新事物的學習能力也比較強」。
  • java大數據和python大數據的全面對比,哪個更主流?
    大數據是目前網際網路流行的技術語言,處理大數據的程式語言比較有優勢的也很多,比如java、python、go、R語言、Hadoop等等,按道理來說每種程式語言都可以處理大數據,只是處理的規模不一樣而且,但是現在比較受歡迎的數據處理程式語言是java與python。
  • c++ fstream + string處理大數據
    java的File,FileReader/FileWriter,BufferedReader/BufferedWriter等類,詳見java讀寫文件(可點擊閱讀原文獲取連結,下同。)(2)應用java的原因是java裡面的map非常靈活,eclipse編譯器更是給力,而且ctrl可以追蹤函數等,詳見java map的排序(3)應用java的另一個原因是java裡面的string類的字符串處理非常靈活,各種函數是應用盡有。
  • Java實現冒泡排序算法
    1.引子1.1.為什麼要學習數據結構與算法?有人說,數據結構與算法,計算機網絡,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中間件,寫的代碼不也能「飛」起來嗎?於是問題來了:為什麼還要學習數據結構與算法呢?
  • 解決JAVA調用C++ DLL文件Unable to load library的問題
    JNI:Java Native Interface是Java平臺的一部分,可用於讓Java和其他語言編寫的代碼進行交互,不過JNI調用過程相當的麻煩。library」,我們從以下幾個方面排除和解決該錯誤:一.
  • 碼農:簡歷千萬別寫精通java,我都被問到字節碼內容上去了!
    那麼關於技術技能這部分又該怎麼寫呢,也有人會說無非就是擁有技術技能的羅列嘛,這倒是沒錯,但是我們常看的簡歷上還對對這些技能加一些形容詞,不是嗎?比如「精通」,「熟悉」,「熟練掌握」,「了解」等,通過這些詞就能大致了解候選人對該技能掌握的程度如何了,既然是這樣,那好多人就會想那就寫精通不就行了,其實這樣的話可能並不是什麼好處,近期就有一名碼農朋友去阿里面試,他的簡歷上寫到精通java, 結果當被問到java部分時,他尷尬了,用他的話說就是已經問到字節碼上去了,說明問到的問題比較有深度了,他實在是接不住了,可見這個「精通」二字也不能隨便寫啊
  • ai本身涉及到的東西有哪些?核心是什麼
    這種前沿技術,現在只要想想就可以了,跟算法這些一點關係都沒有,不要指望能用代碼實現。ai本身涉及到的東西很多,比如語言,算法,機器學習,深度學習,神經網絡等等。只要做出簡單實現的簡單程序,最初也可以編譯到c或者c++或者其他語言。通過簡單實現能帶來的進步,其實也就是vb的代碼量有可能增加那麼一點點。ai本身都是離散的,都是低級工具而已。
  • 藍橋杯簡單java遞歸算法
    4月1號的藍橋杯比賽快來了,報了名的小編日夜操勞的準備著~~~只想默默地說一句,這個算法是真的難~已經不想吐槽它折磨我的這20天了~每當看到自己用很冗長的代碼完成題目java遞歸算法的5道小題。例如:n=3 F(3)=F(2)+F(1),此時需要先調用函數計算F(2)的值,F(2)=F(1)+F(0)=2,之後再返回到F(3)上,則可計算出F(3)=3;具體代碼實現如下:package 遞歸算法;import java.util.Scanner;
  • 為什麼都說java比較容易入門?
    說java比較容易入門的人,應該都是正常的人,因為相比之下,java有著不可比擬的優勢,對於編程新手來說這個優勢,可以讓他們更快的用
  • java 數據結構與算法 之遞歸算法
    從事java開發多年,越來越發現要學的很多,但是有什麼辦法呢,誰叫我們已經走上了這條道路呢。我們也只有一點一點的積累,趁現在有時間,今天討論一下java 的數據結構與算法:遞歸算法,希望能達到溫故而知新的效果。一。
  • 為什麼有些算法崗位,需要用C++而不是python?
    從理論上講算法適用於任何的程式語言,算法在實際工作過程中就是為了工作效率,如果什麼事情都是按照窮舉法或者別的串行的方式效率會太低了,算法能夠極大程度的提升效率算法其實就是執行一系列的指令在規定的時間內拿到輸出結果,從這點看時間是存在邊界的,要講求時效性
  • 九大程式語言優缺點第四期:c++
    C++:c++誕生於1983年,緊隨c語言的步伐,c++是C語言的超集,大家所知道的C語言是面向過程的,java是面向對象的,那麼C語言為了面向對象,所以誕生出現在大家所熟知的c++,被廣泛視為大規模應用構建軟體。
  • 學了1年java的程式設計師面試,掛在了這道基礎算法題!
    這都是java的算法題,應該來講都是些比較簡單的算法題,但是我敢說很多基礎的學習的,或者想去面試的人都會不能完整的寫出來,現在
  • C/C++優勢究竟在哪裡?是什麼讓他們經久不衰?看看這個你就懂了
    而c++可以用來構建搜尋引擎,可以用作軟體開發,作業系統和視頻遊戲,用途還是很廣泛的,幾乎在每個城市都會看到c++的身影。c++語言可以說是相當的難以學習,但是當你熟練的掌握了c/c++以後,那麼在眾多開發人才中就能脫穎而出,直接站在程式設計師金字塔的塔尖,熟練掌握了c++/c會幫助你了解java,python等語言的內存管理機制,並了解如何規避那些各類常見的陷阱和問題,c++允許大家對自己的應用程式進行靈活地調整並充分發揮計算機的全部性能,與java相比,c++的編程過程並不友好,
  • 軟帝學院:Java程式設計師入門必看的 4 本 Java 書籍!
    01《Head First Java》一個星期就能讓你明白怎麼用Java寫程序了。尤其是你有其它語言基礎的情況下,這本書能迅速讓你明白Java的特質。 缺點是,它真的只是入門書。02《Java 核心技術:卷1 基礎知識》
  • Java加密與解密:消息摘要算法MD5
    簡述MD5算法是典型的消息摘要算法,其前身有MD2、MD3和MD4算法,它由MD4、MD3和MD2算法改進而來。這個算法首先對信息進行數據補位,使信息的字節長度是16的倍數。再以一個16位的檢驗和做為補充信息追加到原信息的末尾。最後根據這個新產生的信息計算出一個128位的散列值,MD2算法由此誕生。