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

2021-03-02 人工智慧頭條

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

今天帶來的文章,是 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::string 和 String 的用法對照如下表所示:

基本語法

雖然 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::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。

類和封裝

首先說說 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 在基本語法層面的對應關係,因為算法代碼涉及的語言方面深度有限,所以本文介紹的內容也比較基礎。

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

課程中選擇了三十多個簡單且實用的算法實例,這些算法實例基本覆蓋了各種算法比賽中經常出現的題目以及生活中常見的一些有趣的算法實現。

每個算法實現都將講解的側重點放在各種算法的設計方法和思想在算法中的體現,通過一個個算法例子,來引導大家掌握常見的算法設計思想。

相關焦點

  • 推薦幾款可以直接在手機上編程的app(包含Java、C、Python等)
    點擊上方藍色字體,關注我們這裡介紹幾款可以在手機上編程的app,分別是:1.java大部分都不需要root,可以直接編寫程序並運行,下面我簡單介紹一下這3個app的安裝和簡單使用,主要內容如下:一.AIDE集成開發環境:這個主要是用來寫java代碼(創建工程、寫小遊戲等),當然也可以寫c++代碼,只不過需要安裝對應的插件才行,自帶自動補全的功能,界面乾淨、整潔,使用起來不錯,下面我介紹一下這個app的安裝和簡單使用:
  • 手機APP都是用什麼程式語言寫的呢
    打開APP 手機APP都是用什麼程式語言寫的呢 C語言與程序設計 發表於 2020-12-24 17:05:28   今天想和大家分享的內容是和我們手機上APP相關的,它們都是用什麼程式語言寫的呢?
  • 用java計算數學題真的方便!
    哈嘍大家好,這裡是java小白成長記!最近把基礎的小知識都分享完了,所以這兩天就是各種小例子來強化前面的知識,今天就用java來計算一道數學題:1+2-3+4-5+6-7+8……+100的結果是多少?思路:首先還是找規律捋清思路,上面的數學式子基本都是加偶數減奇數,為什麼是基本?因為1沒有這個規律,所以1要單獨拿出來,剩下的數字我們用if選擇語句來判斷一下,如果是偶數就加上,如果是奇數就減掉,判斷奇偶性很簡單了,對2求餘數就可以了。現在就剩下一個1了,1怎麼處理?
  • Java基礎面試題簡單總結
    Overloaded的方法是可以改變返回值的類型13、Set裡的元素是不能重複的,那麼用什麼方法來區分重複與否呢? 是用==還是equals()? 它們有何區別答:Set裡的元素是不能重複的,那麼用iterator()方法來區分重複與否。
  • 詳解JAVA數據結構與算法:排序算法
    排序算法排序也稱排序算法(Sort Algorithm),排序是將一組數據,依指定的順序進行排列的過程。排序的分類:1) 內部排序:指將需要處理的所有數據都加載到內部存儲器中進行排序。2) 外部排序法:數據量過大,無法全部加載到內存中,需要藉助外部存儲進行排序。
  • 面經:我用三個月,拿到了20萬年薪的offer!
    我想要找一份算法相關的工作,奈何算法不過關,學得不到家,所以就打算先學習java,再往架構的方面走,之後慢慢研究算法。在確定了學習方向之後,我開始上網搜索java相關的信息。當時我試聽了很多機構的課程,但都不是很滿意,直到我在騰訊課堂上刷到了拓哥的課。印象很深刻,當時拓哥在講關於字符串的集訓營課程,我聽完之後馬上就確定了想要選擇拓哥的課。
  • 萬字概覽 Java 虛擬機
    絕大部分情況下用不了這麼大,建議配置為 256kb 即可。棧空間越大,則 JVM 能容納的線程數越少;棧空間越小,可遞歸深度越低。棧幀包含有局部變量表,存儲編譯期可知的基本數據類型以及對象的符號引用等信息,還包含有操作數棧、動態連結、方法出口等信息。Stack 為什麼是線程私有的,這涉及到「棧上分配」和「TLAB」兩個概念。
  • 算法的入門丨最基礎的排序算法,選擇、冒泡、插入
    大家在學習java的過程中一定都會接觸到排序算法,而且排序算法是編程學習的過程中最先接觸的算法。今天給大家帶來算法的一次回憶,排序算法入門的三種基礎算法——選擇排序、冒牌排序和插入排序。選擇排序選擇排序,弟中弟,最沒用的算法,時間複雜度非常之高,所以說這個算法是最沒用的算法。但是這個算法是給編程學習者帶來思路的,用來做算法入門的墊腳石是非常不錯的。排序算法的思路:過濾整個數組,將最小的數換到第一位,重複該過程,直到遍歷數組,結束程序。首先,寫一個循環遍歷數組並將數組中元素列印出來的方法。命名為print( ); 方法。
  • 用哪種程式語言寫的應用漏洞最嚴重 Java還是Python
    用哪種程式語言寫的應用漏洞最嚴重 Java還是Python 機器之心 發表於 2021-01-06 16:19:26 靜態代碼分析安全公司 Veracode 近日發布了一份應用程式分析報告,結果發現比起 JavaScript
  • 20個非常有用的Java程序片段(上)
    20個非常有用的Java程序片段送給你們,希望能對你們有用。
  • Kotlin VS Java:基本語法差異
    為了鼓勵你,我們會給你一個等同的類寫在Kotlin。這太簡單了,不是嗎?基本上,findViewById()方法仍在使用中。 但是沒有必要自己寫。 Kotlin會為你做。          這只是一個小例子,說明如何在Kotlin和Java中使用集合,但你可以看到差別! 你能想像如果我們處理一個大項目的集合,Kotlin會有什麼區別嗎?
  • 貪心算法求解:王者榮耀購買點券最優策略
    待了兩天了,還是覺得屋裡安逸,捨不得離開。不過來了學校自己不會像在家裡那麼懶惰了,每天打卡鞭策自己努力前行,早日達到畢業條件。言歸正傳下面開始描述問題:本人平時比較喜歡玩王者榮耀,最近玩韓信比較多,打野颼颼的,雖然很坑,就想買一個韓信街頭霸王的皮膚。
  • 什麼是加密算法?
    常用的加密算法有對稱加密算法,非對稱加密算法,哈希算法,數字籤名等幾類。    對稱加密顧名思義就是加密和解密是對稱的,加密時用一個秘鑰去加密,解密時用同一個秘鑰去解密,由信息發送方和接收方共同約定一個秘鑰。缺點是風險都在這個秘鑰上面,一旦被竊取,信息會暴露。所以安全級別不夠高。常用對稱加密算法有DES,3DES,AES等。在jdk中也都有封裝。
  • Java中的 "弱" 引用有啥用?
    對於簡單的情況, 手動置空是不需要程式設計師來做的, 因為在java中, 對於簡單對象, 當調用它的方法執行完畢後, 指向它的引用會被從棧中彈出, 所以它就能在下一次GC執行時被回收了。但是, 也有特殊例外.
  • 智能運維:質心算法實現網絡故障無差別自動定位
    作者簡介朱鋒廣東行動網路運維監控專家謝謝大家,其實很榮幸今天來到這個舞臺來講這些東西,我今天的主題是叫智能運維,我們用的質心算法實現網絡故障無差別自動定位我們跟網際網路的企業差別非常大。同樣也是來體現這個複雜性,不僅是說剛才的圖講這個原理業務非常複雜,但是我們移動端還有另外一個特點,我們的網絡是中國最大的,而廣東移動也是我們中國移動基本上全國最大的省級網絡了。
  • Java學到什麼程度才能叫精通?
    我個人覺得「精通」這個詞有點過,一般人是不會說自己精通某個東西,通常用熟練並掌握來說明你對某個技術有研究。下面是我總結的一些初中級Java程式設計師必備的知識:總結:初中級 Java 程式設計師必須掌握的知識。熟練掌握數據結構、算法、作業系統、計算機網絡等基礎知識。