寫算法,用 C++ 還是用 Java ,差別大嗎?

大家好,我是爲人造的智能操碎了心的智能禪師。

今天帶來的文章,是 GitChat 簽約作者王曉華在不斷被讀者吐槽:“好好一本算法書爲什麼要用 C++ 來寫” 時,萬般無奈下憋出來的。

還真別說,面對疾風的時候,總能爆發出作者的鬥志來。所以本文特別的……長,以至於不得不提前跟大家說:TL;DR。

當然,說是這麼說,R 還是要 R 的。因爲這篇文章非常詳細的講述了用兩門語言在寫算法時候的優劣勢,非常值得一讀。

如果你剛好在學習算法,那麼文末也貼心的爲你準備了王曉華的算法課程,算法真的很好玩

全文大約3000字。讀完可能需要下面這首歌的時間

?

好好一本算法書,爲什麼要用 c++ ?

儘管學習 Java 了很長時間,但是因爲工作的需要,很少用 Java 做過大型的項目,所以在公開算法實現的時候,我本能地選擇最擅長的 C++ 語言。我介紹算法用的例子都是用 C++ 編寫的,最終招致讀者吐槽:“好好的一本算法書,爲什麼要用 C++?

兩種語言的對比會放在一起展示,如不做特殊說明,上面的代碼是 C++ 的實現方式,下面的是 Java 的實現方式。 C++ 語法層面使用的版本是 C++11之後的版本,Java 使用的標準是 Java6 之後的版本。

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

因爲歷史原因,同爲 C 語言家族的 Java 和 C++ 語言層面的相似性是有客觀基礎的。

我通常是這樣理解的:Java 是跨平臺的 C++,是一種更好的 C++(是不是有點拉仇恨的感覺)。

基本數據類型

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。

字符串

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

std::stringString 的用法對照如下表所示:

基本語法

雖然 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 循環簡直絲滑的不要不要的。

函數

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

數組

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 也可以這麼做,轉換起來也沒什麼難度。

枚舉

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

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

I/O 系統

C++ 代碼中一般用 std::cinstd::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。

類和封裝

首先說說 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 關鍵字的位置不同,理解上應該不存在任何問題。

總結

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章