C++這麼難,為什麼我們還要學習C++?

linux技術棧 發佈 2024-05-09T20:16:49.331116+00:00

C++ 類似 Objective-C,是 C 語言的超集,它希望儘量向下兼容 C 的一切語法和特性,因此足夠接近硬體底層。

前言

c++ 可算是一種聲名在外的程式語言了。這個名聲有好有壞,從好的方面講,C++ 性能非常好,哪個程式語言性能好的話,總忍不住要跟 C++ 來單挑一下;從壞的方面講,它是臭名昭著的複雜、難學、難用。當然,這樣一來,熟練的 C++ 程式設計師也就自然而然獲得了 「水平很高」 的名聲,所以這也不完全是件壞事。

不管說 C++ 是好還是壞,不可否認的是,C++ 仍然是一門非常流行且非常具有活力的語言。繼沉寂了十多年,並終於發布語言標準的第二版 —— C++11,再那之後,C++ 以每三年一版的頻度發布著新的語言標準,每一版都在基本保留向後兼容性的同時,提供著改進和新功能。

1. 為什麼難學

每次提到 C++ 編程,無論你是使用 C++ 的開發者,還是使用其他程式語言和開發環境的開發者,我們對 C++ 的評價往往都是 「複雜且難學」。為什麼 C++ 會留下這樣的口碑?追根溯源,主要有兩個原因。

第一個原因是 C++ 的包容性,即向前兼容。

C++ 類似 Objective-C,是 C 語言的超集,它希望儘量向下兼容 C 的一切語法和特性(在 C99 標準之前甚至是完全兼容),因此足夠接近硬體底層。但這是把雙刃劍。

雖然 C99 之前語法足夠簡單,但實際使用的複雜性並不低,而 C++ 為了兼容 C 語言的語法付出了很大的代價,並在此基礎上設計並發展出了多範式的編程模型,這意味著可以繼續採用面向過程的編程模式,也可以轉向面向對象。與此同時,現代 C++ 還提供了一組函數式編程工具。

因此,在現代 C++ 得到發展以前,實際開發時到底要選用何種範式或者如何合理組合,一直讓我們很頭痛。

C++ 兼容 C 有什麼代價呢?比如,C 的指針類型聲明就備受 C++ 之父 Bjarne Stroustrup 詬病,但是為了向前兼容,不得不在這種聲明模式下繼續擴展。

第二個原因是 C++ 的設計哲學,「不為任何抽象付出不可接受的多餘運行時性能損耗」。

縱觀 C++40 多年的演進歷程,可以發現每一次演進所支持的都是和編譯時相關的新特性,而相對來說,運行時特性非常少,除了在面向對象的編程模型基礎上提出的多態以外,幾乎再無運行時特性(其他的均以庫的形式提供)。這是因為 C++ 是零成本抽象,也就是說,開發者在使用 C++ 表達抽象概念時,無需忍受多餘的運行時性能開銷。

因此,雖然 C++ 具備很多高級抽象的語法特性,但在設計與具體使用過程中,我們仍然需要考慮各種各樣的問題,包括基礎對象內存模型、虛函數的設計、基於模板的泛型系統、基於模板的靜態反射體系,以及到目前為止都是由編譯器決定可選的垃圾回收(在其他現代語言中可以說是必備的特性了),這就讓我們學習和使用 C++ 變得更複雜了。

的確,這真夠複雜的。一門程式語言必定有其局限性,這也是為什麼 「更為現代」 的 Go 和 Rust 出現了,試圖解決一些問題,特別是安全性方面。

不過作為語言的使用者,你肯定會問,那今後的 C++ 學習和使用會有哪些變化呢?這個問題,有人曾經問過 C++ 之父 Bjarne Stroustrup。

諸如 Go 和 Rust 程式語言新貴,它們在發力解決安全性和易用性方面的問題,規避緩衝區溢出這樣的漏洞,甚至 Linux kernel 也開始考慮或採納對 Rust 的支持,您是否覺得這會成為 C++ 的一個潛在的巨大威脅和挑戰?

他的回答簡單明了。

「每隔幾年,就會出現 C++ 的挑戰者,我相信它們一定會有支持者。但是,C++ 的獨特的語言特性、應用場景,以及 C++ 標準發展的方向,會讓 C++ 繼續茁壯成長。」

我特別喜歡這個回答。是啊,劣勢固然存在,但 C++ 經過歷史的檢驗,在高性能計算、低延遲處理、圖形學領域以及機器學習等前沿技術領域有著難以替代的優勢。

C++ 的 「複雜且難學」 一定程度上取決於向前兼容的能力和設計哲學,但正因如此,維護多年的系統仍然能與全新開發的系統友好地對接和集成,C++ 的包容性和多樣性也讓它極具發展力。

自 C++11 標準誕生以來,我們正式邁入現代 C++ 世界,而 C++20 及後續演進標準作為繼 C++11 之後的又一次重大變革,給我們帶來了新思想、新工具,讓我們從容面對以往難以解決的問題。

2. C++的意義

C++ 程式設計師應該都聽到過下面這種說法:

  • C++ 是一門多範式的通用程式語言。

多範式,是因為 C++ 支持面向過程編程,也支持面向對象編程,也支持泛型編程,新版本還可以說是支持了函數式編程。同時,上面這些不同的範式,都可以在同一項目中組合使用,這就大大增加了開發的靈活性。因此,C++ 適用的領域非常廣泛,小到嵌入式,大到分布式伺服器,到處可以見到 C++ 的身影。

下面是一些著名的用到 C++ 的場合:

  • 大型桌面應用程式(如 Adobe Photoshop、Google Chrome 和 Microsoft Office)
  • 大型網站後台(如 Google 的搜尋引擎)
  • 遊戲(如 StarCraft)和遊戲引擎(如 Unreal 和 Unity)
  • 編譯器(如 LLVM/Clang 和 GCC)
  • 解釋器(如 Java 虛擬機和 V8 JavaScript 引擎)
  • 實時控制(如戰鬥機的飛行控制和火星車的自動駕駛系統)
  • 視覺和智能引擎(如 OpenCV、TensorFlow)
  • 資料庫(如 Microsoft SQL Server、mysql 和 MongoDB)

有些同學可能會覺得,這些應用場景似乎和平時的開發場景有點遠啊!你的感覺是對的。有些傳統上使用 C++ 的場合現在已經不一定使用 C++,最典型的是個人電腦上的桌面應用。以前 Windows 下開發桌面應用常常用 MFC,微軟的 C++ 框架。目前很流行的 Visual Studio Code 主要是用 TypeScript 寫的,不是 C++。

C++ 的傳統領域有被侵蝕的風險,那是因為和它相競爭的語言遠遠不止一個,可以說是上下夾攻。

  • 如果專注性能和最小內存占用的話,C 仍然是首選——嵌入式領域用 C 非常多,而 Linux 也是用純 C 寫的。
  • 如果專注抽象表達和可讀性的話,那 Python 之類的腳本語言則要方便得多。
  • 圖形界面(GUI)編程傳統上是 C++ 的地盤,但近年來 C# 和 JavaScript 占領了很大一部分市場。
  • 遊戲算是 C++ 的經典強項了,但有了 C++ 寫的遊戲引擎,遊戲用 C# 寫也沒啥問題了——你可能不一定知道,Unity 遊戲引擎上的首選開發語言是 C#,而王者榮耀是用什麼遊戲引擎呢?答案正是 Unity —— 所以王者榮耀可以認為是用 C# 開發的。
  • 還有,Go 和 Rust 也加入了戰團,對 C++ 形成了一定的競爭……

目前,跟 C++ 定位差不多、能有直接競爭關係的,也就是既支持高度抽象、又追求高性能的通用程式語言,其實只有 Rust 一種。而 Rust 遠沒有達到跟 C++ 一樣的成熟和普及程度。這也可以從 TIOBE 的排名看出來:C++ 是第 4 位,而 Rust 是第 25 位。

另外,和 C 的兼容性,也是 C++ 的一大優勢。雖然現在很多大型程序都混雜了多種語言,但在小項目里,減少語言的數量可以簡化開發和部署。

3. 什麼時候該用C++

C++ 既然性能又好,又支持抽象,為什麼沒有更流行呢?

C++ 比起 C 來,要更安全,更不容易出現緩衝區溢出這類漏洞,但跟沒有指針概念的語言比起來,它仍然是一種「不安全」的語言。我的個人經驗,完成同樣的功能,C++ 需要的代碼行數一般是 Python 的三倍左右,而性能則可以達到 Python 的十倍以上。

那麼問題來了:你在開發上額外付出的時間,能從性能上省回來嗎?

顯然,這取決於你開發軟體的用途和開發時間。舉個例子,如果你用 Python 開發需要一天,運行需要十秒,並且不需要反覆運行;那麼,轉用 C++ 開發就意味著開發費用也許要增加兩倍,開發加運行的總時間增加兩天,大虧。

反之,如果用 Python 開發還是需要一天,單次運行需要十秒,但是軟體會作為服務長時間運行、每天被調用十萬次。在這種情況下,明顯你就需要多台伺服器來支撐其使用了。這時,如果用 C++ 開發會需要額外的兩天,但跟 Python 相比,部署上有望節約十分之九的硬體和電費 —— 那就很值了。

簡言之,當你的軟體屬於運算密集或者內存密集型,你需要性能、且願意為性能付出額外代價的時候,應該考慮用 C++,特別在你的代碼需要部署在多台伺服器或者行動裝置的場合。反之,如果性能不會成為你開發的軟體的瓶頸,那 C++ 可能就不是一個最合適的工具。

此外,在嵌入式應用的場景,那就根本不是值不值、而是行不行的問題。如果程序完成一個功能不能在指定的若干毫秒、甚至微秒內完成,那產品根本是失敗、不可用的。在這種場合,能和 C++ 競爭的只有 C,但 C 是一種開發效率更低、更需要堆人力的語言了。在嵌入式開發使用 C++ 的最大障礙可能不是技術,而是人力資源——搞嵌入式開發的程式設計師可能大多都習慣使用純 C 了。

由於 C++ 是解決性能問題的利器,短時間裡在市場上沒有真正的競爭對手,對 C++ 的需求會在相當長的時間裡一直存在,尤其在大公司和像金融機構一樣對性能渴求的地方。

順便提一句,C++ 之父 Bjarne Stroustrup 目前就職的地方便是摩根斯坦利。

4. 如何學習C++

作為很多聰明人使用過的語言,C++ 在某些場合也可能被用來炫技,寫出除了本人之外誰都看不懂的高抽象代碼。這恰恰是 Bjarne 想努力抵制的方向。他想讓 C++ 對初學者變得更為友好,也明確提出過,他不希望 C++ 是一種讓人們耍機靈的語言,而是一種讓人們更易於使用的語言。

學習 C++ 語言就像學一門活躍使用中的外語,你不要期望能夠掌握所有的單詞和語法規則 —— 那對於世界上 99.999999% 的人來說是不可能的。但語言是服務於人的,語法規則也是服務於人的,是為了讓人們能夠更好地溝通和表達。雖然 C++ 的每一個新標準都是讓語言從定義和規則的角度變得更複雜,但從用法上來說,新標準允許人們能夠更簡單地表達自己的計算意圖。跟學外語一樣,我們需要的是多看多寫,掌握合適的 「語感」,而不是記住所有的規則。

Bjarne 有一個洋蔥理論: 抽象層次就像一個洋蔥,是層層嵌套的。如果想用較低的抽象層次表達較高的概念,就好比一次切過了很多層洋蔥,你會把自己的眼淚熏出來的。與這個思路相反,教 C++ 往往有一種不好的傾向,從那些瑣碎易錯的底層教起,自底向上,使得很多人常常在尚未領悟到抽象的真諦之前就已經被 C++ 的複雜性嚇翻,從入門到放棄;或者,在學了基本的 C 語法和 class 之後就滿足了,錯過了高級抽象帶來的全新境界。他主張學習應當自頂向下,先學習高層的抽象,再層層剝繭、絲絲入扣地一步步進入下層。如果一次走太深的話,挫折可能就難免了。

最後,分享一個c/c++後端開發的學習知識圖譜(摘自零聲教育的大綱:對標騰訊T9職級技術棧,騰訊認證的)

c++後端開發是一個龐雜的技術棧,因為沒有統一的開發框架並且應用行業非常廣泛。所有涉獵廣泛,這裡就把c/c++後端開發的技術點進行整理總結,看完以後,不會讓你失望的。

  1. 精進基石
  2. 高性能網絡設計
  3. 基礎組建設計
  4. 中間件開發
  5. 開源框架
  6. 雲原生
  7. 性能分析
  8. 分布式架構
  9. 上線實戰

部分試聽視頻

6種epoll的設計方法(單線程epoll、多線程epoll、多進程epoll)

4種內存泄漏的解決方案,每一種背後都有哪些隱藏技術

5000道C++「八股文」,還需要死記硬背嗎?90分鐘梳理清晰

C++後端必讀7個開源項目源碼(redis、mysql、nginx、protobuf...)

需要C/C++ Linux伺服器架構師學習資料加qun812855908獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

1、精進基石,分為四個方面(數據結構,設計模式,c++新特性,Linux工程管理)

數據結構部分

設計模式

C++新特性

linux工程管理

2. 高性能網絡設計(網絡編程,網絡原理,協程ntyco,用戶態協議棧ntytcp)

網絡編程

網絡原理

自研框架: 純c實現的協程(2000行代碼)

自研tcp協議棧

高性能異步io機制io_uring

3. 基礎組建設計,分為3部分, 池式組件,高性能組件,開源組件

池式結構

高性能組件

開源組件

4、中間件開發專欄

redis

mysql

kafka

gRPC

nginx

5. 開源框架

遊戲後端開源框架 skynet

分布式API網關

DPDK

高性能計算CUDA

6、雲原生專欄

docker

kubernetes

7、性能分析專欄

性能與測試工具

觀測技術bpf與ebpf

內核源碼機制

8、分布式架構專欄

rocksdb

TiDB

分布式服務

9、上線項目實戰(可以寫入簡歷的兩個實戰項目,讓面試不再為沒有項目發愁)

1、圖床共享雲存儲

2、微服務即時通訊

關鍵字: