為什么會有這篇文章,?無論是七、八年前開始寫的 “算法博客”,,還是三年前出版的圖書《算法的樂趣》,,亦或是暢銷課《算法應(yīng)該怎么“玩”?》,,我介紹算法用的例子都是用 C++ 編寫的,。 盡管以前博客的留言里不乏抱怨之聲,但是在《算法的樂趣》出版的時候我仍然 “一意孤行” ,,最終招致讀者吐槽:“好好的一本算法書,,為什么要用 C++?” 我的很多使用 Java 的朋友們也都為我 “打抱不平”,,但是在事實面前,,我不得不承認(rèn),這個拖了很久的需求必須要實現(xiàn)了。 盡管學(xué)習(xí) Java 了很長時間,,但是因為工作的需要,,很少用 Java 做過大型的項目,所以在公開算法實現(xiàn)的時候,,我本能地選擇最擅長的 C++ 語言,。 事實上,我在編寫《算法應(yīng)該怎么“玩”,?》的過程中,,參考資料里有不少 Java、Python 實現(xiàn)的算法,,原理都是相通的,,用何種編程語言實現(xiàn)只是對外展示的一張皮而已。 作為一個 Java 程序員時,,經(jīng)常會遇到 C++ 算法代碼,,有時候沒有更好的替代例子的情況下,還必須得啃一啃 C++,。 往好處想,,雖然 C++ 涉及的內(nèi)容廣泛,但是算法上能用到的部分并不多,,都是一些基本語言元素,,短時間內(nèi)了解一下 C++ 相關(guān)的內(nèi)容,能夠看懂小段的 C++ 代碼是完全有可能的,。 接下來,,我會在算法涉及的層面上,比較一下 C++ 和 Java 上的差異,,通過這些對比和比較,,Java 程序員能快速理解 C++ 算法實現(xiàn)的例子,C++ 程序員也能看懂簡單的 Java 算法代碼,。
C++ 和 Java 語法特性的相似性因為歷史原因,同為 C 語言家族的 Java 和 C++ 語言層面的相似性是有客觀基礎(chǔ)的,。 我通常是這樣理解的:Java 是跨平臺的 C++,,是一種更好的 C++(是不是有點拉仇恨的感覺)。 基本數(shù)據(jù)類型C++ 的基本數(shù)據(jù)類型有:int、unsigned int,、long,、unsigned long、short,、unsigned short,、char、unsigned char,、bool,、float 和 double; 相應(yīng)的,,Java 也有 8 種基本數(shù)據(jù)類型,,分別是:byte、short,、int,、long、float,、double,、char 和 boolean。
首先是 char,,C++ 的 char 是 8 比特?zé)o符號整數(shù),順便表示了 ASCII 字符,;Java 的 char 是 16 比特,,天生就可以表示寬字符集的字符。 另一個需要注意的是 long 類型,,C++ 的 long 是不可移植類型,,在不同的系統(tǒng)上其長度不一樣,可能是 32 位,,也可能是 64 位,所以 C++ 程序員應(yīng)盡量避免使用 long,。 Java 的 long 比較單純,,無論是 32 位的系統(tǒng)還是 64 位的系統(tǒng),它都表示 64 位整數(shù),。 反過來,,Java 會用 d 或 D 表示一個直接數(shù)字是 double 類型的浮點數(shù),比如 200.0d 或(200.0D),但是 C++ 不支持,,因為 C++ 默認(rèn)一個浮點型的直接數(shù)字就是 double 類型,。 C++ 用字面量符號 f 或 F 表示一個直接數(shù)字是 float 類型浮點數(shù),比如 250.1 f(或 250.1F),,這一點 Java 也是一樣的,。 C++ 用字面量符號 l 或 L 表示 long,用 UL 表示 unsigned long,。 字符串很多 C++ 程序員喜歡的用 char* 或 char 類型的數(shù)組存儲字符串,,這其實是 C 語言用戶帶過來的習(xí)慣,我給出的 C++ 算法實現(xiàn)對字符串一般都用
基本語法雖然 Java 的語法和 C++ 十分地相似,但是語言層面還有一些不同,。C++ 允許全局函數(shù)的存在,,但是 Java 不允許,不過 Java 也留了個口子,,就是用靜態(tài)成員函數(shù),。 Java 沒有指針,對象的傳遞和返回都是用的引用的方式,,并且不需要像 C++ 那樣用 “&” 做特殊的語法標(biāo)記,。 大多數(shù)介紹 Java 的書籍開篇就是類和抽象,然后才是基本的語法,,這和 Java 上等人的氣質(zhì)是一致的,,連這都不會,咋做程序員,?C++ 應(yīng)該多提升一下氣質(zhì),,少用點指針和全局函數(shù)。 不過本文是為了對比 C++ 和 Java 的相似性,,所以就從基本語法結(jié)構(gòu)開始介紹,。 運算符和賦值二者的運算符幾乎一樣,甚至 “++” 和 “—” 運算符都一樣有前綴式和后綴式兩種形式,,意義也一樣,;運算符的優(yōu)先級規(guī)則也是一樣的。 賦值語句兩者基本上是一樣的,,看看每一行結(jié)尾的 “;” 你就知道它們有多相似,。 條件判斷與循環(huán)條件判斷方面,C++ 與 Java 的 if 語句,、switch 語句用法都相同,;邏輯表達(dá)式的結(jié)構(gòu)和語法,、邏輯運算符的優(yōu)先級也都相同。 C++ 的三種基本循環(huán)方式是 while 循環(huán),、do…while 循環(huán)和 for 循環(huán),,Java 都支持,甚至連關(guān)鍵字和 break,、continue 控制語句的意義也一樣,。 C++11 版本引入了一種根據(jù)范圍循環(huán)的語法,一般理解和 Java 的增強 for 循環(huán)類似,,比如這種 C++ 循環(huán)形式: Java 與之對應(yīng)的形式是: C++ 的基于范圍的 for 循環(huán)也可用于 C++ 的標(biāo)準(zhǔn)庫對象,,用于取代老舊的迭代器循環(huán)方式: 同樣,Java 的增強 for 循環(huán)也支持基于 Collection 的遍歷,,理解起來不成問題: 傳統(tǒng)的 C++ 語言是用迭代器對標(biāo)準(zhǔn)庫的容器進(jìn)行遍歷,,比如: C++ 的容器都有 C++ 用當(dāng)前迭代器的值是否等于 Java 的 Collection 也有迭代器的機制,,Java 用 C++ 直接用 “ * ” 提領(lǐng)迭代器,得到對象本身的引用,,Java 用迭代器的 除了以上的 for 循環(huán)語句,C++ 還支持 第三個參數(shù)是一個可調(diào)用對象,,即函數(shù)對象(C++11 版本之后,這個參數(shù)還可以是一個 Lambda 表達(dá)式),,舉個栗子: Java 沒有與之對應(yīng)的泛型函數(shù)接口,,但是 Java 的很多 Collection 都支持 C++ 的 函數(shù)C++ 的函數(shù)結(jié)構(gòu)和 Java 也一樣,,函數(shù)調(diào)用的形參和實參對應(yīng)方式也一樣,也無需多做說明,。 數(shù)組C++ 和 Java 都支持原生數(shù)組,,并且數(shù)組索引都是從 0 開始。 C++ 中定義數(shù)組的同時就分配了存儲空間,,所以在定義時要指定長度,,使用 new 動態(tài)申請內(nèi)存時,要指定長度,。 但是一種情況除外,,那就是靜態(tài)初始化數(shù)組的形式,因為此時編譯器知道需要多少空間存儲這些數(shù)據(jù),,如下是 C++ 定義數(shù)組的常用形式: Java 如果僅僅是聲明一個數(shù)組,,可以不指定長度,因為此時并不分配存儲空間,,需要分配空間的時候,,用 new。與之對應(yīng)的 Java 語言的形式是: C++ 中二維數(shù)組的每一維長度必須相同,,因為 C++ 的二維數(shù)組實際上只是一塊連續(xù)的存儲空間而已,,甚至可以用一維數(shù)組的下標(biāo)遍歷全部二維數(shù)組的存儲空間。 Java 沒這要求,,因為 Java 的每一維都是可以單獨申請存儲空間的,。但是二者在使用形式上是一樣的。C++ 定義和初始化二維數(shù)組一般有這幾種形式: 與之對應(yīng)的 Java 語言初始化二維數(shù)組的形式是: C++ 也支持動態(tài)內(nèi)存形式的二維數(shù)組,,一般有兩種使用方法,,Java 都有與之對應(yīng)的習(xí)慣用法: 與之對應(yīng)的 Java 的方法是: 這代碼相似度很高。C++ 代碼最后要用 C++ 還可以利用二維數(shù)組在內(nèi)存中是連續(xù)存儲這一特性,使用時用下標(biāo)計算將一維數(shù)組當(dāng)成二維數(shù)組使用,,計算的方法是: 遇到這樣的代碼,需要根據(jù)上述對應(yīng)關(guān)系,,小心地理解算法代碼的意圖,。一些棋盤類游戲通常喜歡用一維數(shù)組存儲二維的邏輯棋盤結(jié)構(gòu),好在 Java 也可以這么做,,轉(zhuǎn)換起來也沒什么難度,。 枚舉與 C 相比,C++ 強化了類型差異,,枚舉變量和整數(shù)變量之間不能互相賦值,,但是使用方法依然是直接使用枚舉值,沒有限制域,。 C++11 之后,,開始支持強類型枚舉,,這一點就和 Java 很像了,越來越像一家人了: I/O 系統(tǒng)C++ 代碼中一般用 也有一些半吊子 C++ 程序員會在 C++ 代碼中混用 C 語言的 不過話說回來,很多語言都支持 printf 方式的格式化輸出,,比如 Java,、 Python,為啥 C++ 就不能提供一個呢,?比如以下代碼接受用戶輸入一個字符串和一個整數(shù),,并將其輸出出來: 將其翻譯成 Java,是這個樣子的: 上述代碼示例中,,C++ 和 Java 的輸入分隔符都是空格或回車,,如果希望輸入帶空格的一整行內(nèi)容怎么辦? C++ 提供了 結(jié)束符默認(rèn)是 Java 也有與之對應(yīng)的 Buffer IO 方式,請看: C++ 程序員有時候也會用 但是 為了適應(yīng)它的這個小個性,,C++ 程序員通常會在后面跟一個 get,將結(jié)束符讀出并丟棄掉,,所以代碼看起來有點怪怪的: 理解了這一點,,看懂 C++ 代碼也就不難了。當(dāng)然,,無論是 C++ 還是 Java,,其 I/O 系統(tǒng)都非常復(fù)雜,有流式 I/O,也有緩沖區(qū) I/O,,操作的數(shù)據(jù)可以是控制臺 I/O,,也可以是文件 I/O。 類和封裝首先說說 C++ 的 struct,,Java 沒有與之對應(yīng)的相似物的,,但是完全可以用 class 來替換這個概念。為什么這么說呢,? 因為在 C++ 中,struct 的位置有點尷尬,,它是個 POD 吧,,但它的成員又可以用非 POD 的數(shù)據(jù)類型,比如 在我看來,,C++ 保留 struct 的主要意義是為了兼容舊的 C 或 C++ 的庫,,這些庫中很多接口用到了 struct,其次是純粹作為一種 POD 的 value type 來使用,。 我的算法代碼中也會用到 struct,,大概是為了懷舊吧,其實完全可以用 class 代替,,當(dāng)然也可以很容易地翻譯成 Java 的 class,。來看個例子,對于這個 struct: 可以很輕松的轉(zhuǎn)成 class: 自從 C++11 發(fā)布以后,,我就覺得 C++ 和 Java 的 class 越來越像了,,分開這么多年后,C++ 終于也支持 final 和 override 了,。 從語法層面看,,二者的差異很小,就小規(guī)模的算法而言,,也很少會用到繼承和重載之類的情況,,所以,Java 程序員看懂 C++ 的 class 定義與實現(xiàn)一點都不難,。 少有的一些差異,,比如 C++ 的函數(shù)可以設(shè)置參數(shù)默認(rèn)值,或者 C++ 的抽象機制采用的虛函數(shù)要使用 virtual 關(guān)鍵字等,。先看一個典型的 C++ 類的定義與實現(xiàn): C++ 的類成員訪問控制采用分節(jié)控制,,用 C++ 的成員函數(shù)可以有默認(rèn)值,,并且構(gòu)造函數(shù)也支持默認(rèn)值。以 Bucket 類為例,,構(gòu)造函數(shù)第二個參數(shù)默認(rèn)值是 0,,即在構(gòu)造 Bucket 對象時,可以只確定一個參數(shù) capicity,,也可以在確定 capicity 參數(shù)的同時,,確定 Bucket 的水量,比如: Java 不支持參數(shù)默認(rèn)值,,但是可以通過重載函數(shù)解決這個問題,,即增加一個只有 capicity 參數(shù)的構(gòu)造函數(shù): C++ 沒有抽象基類的語法,但是又抽象基類的概念,,一般當(dāng)一個類中有一個純虛函數(shù)的時候,,這個類是不能被直接實例化的,它就類似于是一個抽象基類,,比如: C++ 的函數(shù)有很多類型修飾,,比如常見的 const,C++11 后新增了 final 和 override,,但是 = 0 一直是一個比較奇怪的存在,,它表明這個函數(shù)沒有實現(xiàn),需要在派生類中實現(xiàn),,同時,,也說明這個類是不能被實例化的。 對于這樣的機制,,Java 可以理解為這就是個抽象基類: C++ 的繼承體系的語法與 Java 類似,,只是語法形式上不同,Java 采用關(guān)鍵字: C++ 對于基類聲明的虛函數(shù),,繼承類中不需要再用 virtual 關(guān)鍵字修飾,當(dāng)然,,加了 virtual 關(guān)鍵字也沒錯誤,。Java 也一樣,abstract 關(guān)鍵字再繼承類中可以省去,。 從 Shape 抽象類派生一個 Circle 類,,C++ 的典型代碼是: Circle 構(gòu)造函數(shù)后面的 : 以上代碼翻譯成 Java 語言,應(yīng)該是這樣的形式: C++ 有時候也會將一個類聲明為 final,,意味著它不希望被其他類繼承,,從語法上做了限制,比如: 有時候,,是某個不希望被派生類重載,,比如: 這些對于 Java 程序員來說,并不陌生,,語法上只是 final 關(guān)鍵字的位置不同,,理解上應(yīng)該不存在任何問題。 總結(jié)本文介紹了 C++ 和 Java 在基本語法層面的對應(yīng)關(guān)系,,因為算法代碼涉及的語言方面深度有限,所以本文介紹的內(nèi)容也比較基礎(chǔ),。 通過對比發(fā)現(xiàn)不管是用 C++ 還是用 Java 來寫算法,,差別基本不大,如果朋友們對算法想再深度了解,,可以看一下《算法應(yīng)該怎么“玩”,?》。 課程中選擇了三十多個簡單且實用的算法實例,,這些算法實例基本覆蓋了各種算法比賽中經(jīng)常出現(xiàn)的題目以及生活中常見的一些有趣的算法實現(xiàn),。 每個算法實現(xiàn)都將講解的側(cè)重點放在各種算法的設(shè)計方法和思想在算法中的體現(xiàn),通過一個個算法例子,,來引導(dǎo)大家掌握常見的算法設(shè)計思想,。 |
|