我讀研期間學的 Windows C++ 開發(fā),畢業(yè)第一份工作很挑,非 Windows C/C++ 不做,后來做了幾年 Windows C++ 技術負責人,再后來又轉行做 Linux C++,又做了幾年 Linux C++ 主程,再后來做了 Java 開發(fā),如今作為負責人,根據業(yè)務做技術選型,需要哪種語言就用哪種語言。但是我最喜歡的還是 Windows C++,C++ 學好之后真的能對你的技術基礎起的非常好的促進作用。
分享下我個人經驗和感受吧
寫在前面的話
在大多數(shù)開發(fā)或者準開發(fā)人員的認識中,C/C++ 是一門非常難的編程語言,很多人知道它的強大,但因為認為“難”造成的恐懼讓很多人放棄。
我從學生時代開始接觸 C/C++,工作以后先后擔任過 C++ 客戶端和服務器的開發(fā)經理并帶隊開發(fā),至今已經有十多年了。雖然時至今日哪種編程語言對我來說已經不再重要(我目前主要從事 Java 開發(fā)),雖然時至今日哪種編程語言對我來說已經不再重要(我目前主要從事 Java 開發(fā)),其妙無窮,就像武俠小說中的“九陽神功”一樣,有了這個基礎,您可以快速學習任何語言和編程技術。
目前 C/C++ 的應用領域
需要注意的是本文不細分 C 與 C++ 的區(qū)別,通常情況下,C++ 可以看成是 C 的一個超集,在古典時期,可以認為 C++ 就是 C with classes。雖然如今的 C++從功能層面上來看,離 C 越來越遠了;但是從語法層面來上來看,大多數(shù) C++ 語法還是與 C 基本一致的,所謂 C++ 的面向對象特性,如果細究 C++ 類方法的具體語法還是 C 的過程式語法。當然,面向對象是一種思想,語言本身對其支持的程度固然重要,能否熟練地使用則是更要看開發(fā)者的水平了。
C 語言目前主要用于像操作系統(tǒng)這樣一類偏底層的的應用開發(fā),包括像 Windows/linux等這樣的大型商業(yè)操作系統(tǒng),和嵌入式操作系統(tǒng)、嵌入式設備上的應用。還有一些開源的軟件,也會選擇C開發(fā),這些系統(tǒng)主要優(yōu)先考慮程序執(zhí)行效率和生成的可執(zhí)行文件的體積(C 代碼生成的可執(zhí)行文件體積相對更?。?,當然還有一些是歷史技術選型問題,這類軟件以 redis、libevent、nginx,目前像國內的電信服務商所使用的電話呼叫系統(tǒng)一般也是基于一款叫 freeswitch 的開源 C 程序做的二次開發(fā)。
C++ 面向對象的語法與 C 相比較起來,在將高級語言翻譯成機器二進制碼的時候 C++ 編譯器在背后偷偷地做了大量工作,生成了大量的額外的機器碼,而這種機器碼相對于 C 來說是不是必須的。例如,對于一個 C++ 類的實例方法,編譯器在生成這個方法的機器碼時,會將函數(shù)的第一個參數(shù)設置成對象的 this 指針地址,以此來實現(xiàn)對象與函數(shù)的綁定。正因為如此,許多開發(fā)者會優(yōu)化和調整編譯器生成的匯編代碼。
C++ 與操作系統(tǒng)平臺
從上面的介紹可以看出,與 Java、python 等語言相比,C/C++ 語言是運行在離操作系統(tǒng)最近的一種高級語言,因此其執(zhí)行效率也比較高,但是有得必有失,也因為如此,所以 C/C++ 這門語言存在如下特點:
C/C++ 整套的語法不具備“功能完備性”,單純地使用這門語言本身提供的功能您無法創(chuàng)建任何有意義的程序,您必須借助操作系統(tǒng)的 API 接口函數(shù)來達到相應的功能。當然,隨著 C++ 語言標準和版本的不斷更新升級,這種現(xiàn)狀正在改變;而像 Java、python 這類語言,其自帶的 sdk 提供了各種操作系統(tǒng)的功能。舉個例子,C/C++ 語言本身是不具備網絡通信功能的,必須使用操作系統(tǒng)提供的網絡通信函數(shù)(如socket系本身是不具備網絡通信功能的,必須使用操作系統(tǒng)提供的網絡通信函數(shù)(如socket系整的網絡通信功能。我在讀書的時候常常聽人說,QQ、360 安全衛(wèi)士這類軟件是用 C/C++ 開發(fā)的,但是當我學完整本 C/C++ 教材以后,仍然寫不出來一個像樣的窗口程序。許多過來人應該都有類似的困惑吧?其原因是一般 C/C++ 的教材不會教你如何使用操作系統(tǒng) API 函數(shù)的內容。
正因為 C/C++ 語言需要直接使用了操作系統(tǒng)的接口功能,這就造成了 C/C++ 語言繁、難的地方,如操作內存方面不當容易引起程序宕機,不同的操作系統(tǒng)的 API接口使用習慣和風格也不一樣,接口函數(shù)種類繁多,開發(fā)者如果想開發(fā)跨平臺的程序,必須要學習多個平臺的接口函數(shù)和對應的系統(tǒng)原理。
在應用層開發(fā),直接使用操作系統(tǒng)的接口的函數(shù),往往執(zhí)行效率高,控制力度大,您的開發(fā)能力僅僅限制于操作系統(tǒng)本身,Java 這類語言,很多功能即使操作系統(tǒng)提供的,如果 Java 虛擬機不提供,開發(fā)人員也無法使用。正如著名的編程大師 Charles Petzold 說的:
“顯而易見,究竟用哪種方式編寫應用程序最好,其實并無一定之規(guī)。應用程序本身的特性應該是決定采用何種編程工具的最主要因素,但是無論將來你采用什么樣的編程工具,通過了解操作系統(tǒng) API 從而深入理解操作系統(tǒng)的工作原理,這本身就有很重要的意義。操作系統(tǒng)是一個非常復雜的系統(tǒng),在 API 之上加一層編程語言并不能消除其復雜性,最多不過是把復雜性隱藏起來而已。說不定什么時候,這種復雜的那一面遲早會蹦出來拖你的后腿,懂得系統(tǒng) API 能讓你到時候更快地掙脫困境。 在基本操作系統(tǒng)API之上的任何軟件層或多或少都會限制你使用操作系統(tǒng)的全部功能。比如,你或許發(fā)現(xiàn)采用 Visual Basic 來編寫你的應用程序非常理想,但是就有那么一兩項非?;镜墓δ?Visual Basic 無法支持。往往這個時候你得非要調用基本 API 。作為直接使用操作系統(tǒng) API 的程序員,我們的活動空間完全由 API 來規(guī)范,再沒有什么其他方式比直接調用 API 更有效、更靈活多樣了。”
總結起來,C/C++ 語言的開發(fā)核心是建立在直接調用操作系統(tǒng) API 的基礎上的,優(yōu)點是執(zhí)行效率高、發(fā)揮空間大;缺點是,需要經過系統(tǒng)深入的學習,學習周期長,編寫代碼較復雜、容易出錯。
Linux C++ 與 Windows C++ 領域之爭
我之所以把這一個標題單獨列出來,是想糾正現(xiàn)在很多 C/C++ 新人和初學者的一些的不當認識,一般有以下幾種觀點:
1.Linux C++ 開發(fā)就是后臺開發(fā),而 Windows C++ 開發(fā)就是客戶端開發(fā);
2.后端開發(fā)比客戶端開發(fā)(前端)高級,因此后端開發(fā)行業(yè)薪資水平比客戶端開發(fā)薪資要高;
3.我只學 Linux,不學 Windows。
我相信對于 80 和 90 這一代的開發(fā)者來說,當初接觸計算機并進入軟件行業(yè),都是從接觸 Windows 開始的。時至今日,大數(shù)據、人工智能等各種新技術方興未艾,移動互聯(lián)網如火如荼。但是無論是 Linux 還是 Windows,尤其是 Windows,還是我們大多數(shù)人工作、學習、娛樂使用最多的操作系統(tǒng),我們每天都會使用上運行在其上的各種軟件。我們使用這些軟件像喝水、呼吸空氣一樣自然,所以很多人就忽視了這類軟件的“基礎作用”。對于 Windows 上的軟件開發(fā)由于其發(fā)展了很多年了,這些領域也比較成熟,一般不再招初中級開發(fā),而是需要水平較高、經驗較豐富的高級開發(fā)者,這讓很多人造成了“Windows C++”開發(fā)市場需求已經很小了的錯覺。試問,PC QQ 部門這些年對外招了多少人?
另外,Linux C++ 和 Windows C++ 一樣,沒有孰高孰低之分,只是兩種不同的操作系統(tǒng)而已,不要覺得在 Linux 下敲命令就比在 Windows 的圖形化界面點擊鼠標達高級。圖形化界面之于命令行,是人們對更高級、更方便的工具的追求的必然結果。Linux C++ 也不一定就是后臺開發(fā),Windows C++ 也不一定就是客戶端開發(fā);所謂的服務器與客戶端是個相對的概念,即誰給誰提供服務,提供服務的我們認為是服務端(后臺),被服務的我們認為是客戶端(前臺)。而 Windows 作為后臺服務的應用也比比皆是,如筆者之前所在的某交易所的服務器后臺都是 Windows 下的 C++ 程序;另外如一些游戲類的服務器端,也不少是 Windows 的。
借用《UNIX 編程藝術》這本書的觀點,Windows 和 Linux 的哲學理念不一樣,Windows 是假設你不會操作,它教你如何操作,而 Linux 是假設你會操作然后進行操作;根據這個理念,Windows 一般普通人用的多,而 Linux 程序員用的多。從編程的角度來說,Windows 的代碼風格是使用所謂的匈牙利命名法,而 Linux 使用的短小精悍的連字符風格,例如同一個表示屏幕尺寸的整型變量,Windows 上可能被命名為 iScreen 或 cxScreen ,而 Linux 可能是 scrn;再例如 Windows 上創(chuàng)建線程的函數(shù)叫 CreateThread, ,Linux 下叫 pthread_create。有時候,我覺得 Windows 的,匈牙利命名法反而更容易理解代碼。
這里既然提到前端(客戶端)開發(fā)和后端開發(fā),這里不得不提一下,這二者沒有優(yōu)劣之分。其側重點和開發(fā)思維是不一樣的,前端(客戶端)開發(fā)一般有較多的界面邏輯,它們是直接與用戶打交道的,因而一款客戶端軟件的好壞很大程度上取決于其界面的易用性和流暢性,開發(fā)者只要把這一端的“一畝三分地”給管理好即可;而后端服務,對于普通用戶是透明的,開發(fā)者的程序必須盡量體現(xiàn)“服務”這個字眼,即更有效地為更多的客戶端服務,這就要求兼顧請求響應的正確性、及時性和流暢性,由于服務軟件也是運行在某臺物理機器上的程序,鑒于 CPU、內存、網絡帶寬資源有限,而服務程序一般是長周期運行的,因此必須合理的分配和使用資源(如盡量回收不再使用的各種資源),開發(fā)者應從全局考慮,不能在某個“客戶端”這一棵樹上“吊死”。
從個人的職業(yè)發(fā)展來看,建議從事客戶端開發(fā)的讀者適當?shù)亓私庖幌路掌鏖_發(fā)的思路,反過來也建議從事后端開發(fā)去學習一下客戶端開發(fā),二者相得益彰。從個人的技術提高來說,也是很有幫助的。例如您要學習一套開源的軟件代碼,如果您熟悉客戶端和服務器的基本開發(fā)和調試技巧,您可以更好地學習它。而在工作上,一個項目,往往是由客戶端和服務器程序組成,如果您都熟悉,您可以站在一個更高的角度去審視它、規(guī)劃它,這也是架構師的基本要求之一。
最后就是很多讀者關心的客戶端和服務器的薪資問題,這個沒有絕對的誰高誰低,因人而異,因能力而異,因崗位而異。
如何看待 C++ 11/14/17 新標準
C++ 開發(fā)者有個不成文的規(guī)定就是,即使您對 C++ 很熟悉,也不要在簡歷上寫上您精通 C++,原因很簡單—— C++ 這門語言包含的東西實在太多了,沒有人能真正“精通”所有。C++ 既支持面向對象設計(OOP),也支持以模板語法為代表的泛型編程(GP)。而且新的 C++ 標準和遵循 C++ 新標準的編譯器也參出不窮,這些年,C++ 變化越來越大,越來越快,從最初業(yè)界和開發(fā)者翹首以盼的 C++11 標準,歷經 C++14、C++17 到今天的 C++20,這門語言與之前的版本差別越來越大,更多原來需要使用第三庫的功能也被陸續(xù)添加到 C++ 標準庫中。以致于C++之父 Bjarne Stroustrup 也開始對這門語言表示擔憂:
在 C++11 開始的基礎建設尚未完成,而 C++17 基本沒有在使基礎更加穩(wěn)固、規(guī)范和完整方面做出改善。相反,卻增加了重要接口的復雜度(原文為 surface complexity,直譯“表面復雜度”),讓人們需要學習的特性數(shù)量越來越多。C++ 可能在這種不成熟的提議的重壓之下崩潰。我們不應該花費大量的時間為專家級用戶們(比如我們自己)去創(chuàng)建越來越復雜的東西。(還要考慮普通用戶的學習曲線,越復雜的東西越不易普及。)
當然,我們不用有這種擔憂,畢竟我們既不是 C++ 標準委員會成員,也不是 C++ 編譯器開發(fā)廠商。就我個人經驗來說,對于 C++11、C++14、C++17 乃至 C++20,我們學習它們的準則應該是以實用為主,也就是說我們應該學習其實用的部分,至于新標準提到的一些高級特性和各種復雜的模板,我們大可不必去了解。我們并不是做學術研究,我們學習 C++ 是為了投入實際的生產開發(fā),所以應該去學習 C++ 新標準中實用的語法和工具庫。關于C++11常用一些知識點,這里也簡單地給讀者列舉一下:
auto 關鍵字、for-each 循環(huán)、右值及移動構造函數(shù) + std::forward + std::move + stl 容器新增的 emplace_back() 方法、std::thread 庫、std::chrono 庫、智能指針系列(std::shared_ptr/std::unique_ptr/std::weak_ptr)(智能指針的實現(xiàn)原理一定要知道,最好是自己實現(xiàn)過)、線程庫 std::thread + 線程同步技術庫
std::mutex/std::condition_variable/std::lock_guard 等、lamda 表達式(JAVA 中現(xiàn)在也常??疾?lamda 表達式的作用)、std::bind/std::function 庫、其他的就是一些關鍵字的用法(override、final、delete),還有就是一些細節(jié)如可以像 JAVA 一樣在類成員變量定義處給出初始化值。
C++語言基礎與進階
基礎
這里說的基礎不是狹義上的 C++ 語言基礎,而是包括 C++ 開發(fā)這一生態(tài)體系的基礎,筆者認為的基礎有:
1.C++ 語言本身熟練使用程度
2.前面也介紹了單純的 C++ 您啥也干不了,您必須結合一個具體的操作系統(tǒng)平臺,所以您得熟悉某個操作系統(tǒng)平臺的 API 函數(shù),比如 Linux,以及該操作系統(tǒng)的原理。這里說的操作系統(tǒng)的原理不局限于您在操作系統(tǒng)原理圖書上看的知識,而是實實在在與系統(tǒng) API 關聯(lián)起來的,如熟練使用各種進程與線程函數(shù)、多線程資源同步函數(shù)、文件操作函數(shù)、系統(tǒng)時間函數(shù)、窗口自繪與操作函數(shù)(這點針對 Windows)、內存分配與管理函數(shù)、PE 或 ELF 文件的編譯、鏈接原理等等。
3.網絡通信,網絡通信在這里具體一點就是 socket 編程。這里的 socket 編程不僅要求熟練使用各種網絡 API 函數(shù),還要求理解和靈活運用像三次握手四次揮手等各種基礎網絡通信協(xié)議與原理。關于 socket 編程實踐,《TCP/IP網絡編程》這本書是非常好的入門教材。
說了這么多,您可能會覺得很抽象。筆者在這里舉個具體例子,假設我們現(xiàn)在要開發(fā)一個類似電驢這樣的軟件。軟件界面如下圖:
如上圖所示,假設我們的操作系統(tǒng)選擇 Windows,使用語言我們使用 C++,這就要求您必須熟悉 C++ 常用的語法,如果您還不熟悉,您就需要補充這方面的知識。
在熟悉 C++語法的前提下,從這款產品實現(xiàn)技術來看,我們的目標產品分為 UI 和網絡通信部分。下面將詳細介紹這兩部分:
UI 部分
對于 UI 部分,我們的認識是這需要使用 Windows 的窗口技術。我們可以直接使用原生的 Win 32 API 來制作自己的界面庫,也可以選擇一些我們熟悉的界面框架,如 mfc,wtl、duilib、wxWidgets 等。無論您是在閱讀別人的這樣的項目還是需要自己開發(fā)這樣的項目,在確定了這款軟件使用的 UI 庫(或者使用原生 Win 32 API),您就需要對 Windows 的窗口、對話框、消息產生、派發(fā)與處理機制需要了解,同樣的道理,如果不熟悉您需要補充相關的知識(關于這一點,下文不再贅述)。
接著,根據上圖中的軟件功能,大致分為三大模塊,即資源、下載和分享。這三大塊是可以使用一個 Windows Tab 控件去組織,這個時候您需要了解 Windows Tab 控件的特性。
1.對于資源模塊,本質上是一個窗口中嵌入了一個瀏覽器控件(WebBrowser 控件),那么您需要了解這一個功能點的相關知識。當用戶點擊了某個列表中某個具體的資源,可以對齊進行下載。這就又涉及到 WebBrowser 控件與 C++ 宿主程序的交互了,那么如何實現(xiàn)呢?可以選擇使用 ActiveX 技術,也可以使用 javascript 與 C++交互技術。
2.再來看下載模塊,當產生一個下載操作時,界面上會產生以下下載列表,每個列表項會實時顯示下載進度、下載速率等參數(shù),同時正在下載的項目也可以被暫停、停止和刪除。那么這又設計到 ListView 控件的相關功能,以及 ListView 如何與后臺網絡通信邏輯交互。
3.分享模塊是將本地資源分享到服務器或者給其他用戶。界面左側是對文件系統(tǒng)的一個快照,那么這又涉及到如何遍歷文件系統(tǒng)(了解枚舉文件系統(tǒng)的 API),右側也是一個 ListView 控件,這里不再贅述。
網絡通信部分
網絡通信部分,主要有兩大塊,第一個是程序啟動時,與服務端的交互;第二個就是文件下載與分享的 P2P 網絡。您在閱讀或開發(fā)的過程中,如果對這些技術比較陌生,您需要補充這些知識,具體的也就是 socket 的各種 API 函數(shù),以及基于這些 API 邏輯的組合。當然可能也會用到操作系統(tǒng)平臺所特有的網絡 API 函數(shù),如 WSAAsyncSelect 網絡模型。
再一點,網絡通信部分如何與 UI 部分進行數(shù)據交換,是使用隊列?全局變量?或者相應的 Windows 操作平臺提供的特殊通信技術,如 PostMessage 函數(shù)、管道?如果使用隊列,多線程之間如何保持資源的一致性和解決資源競態(tài),使用 Event、CriticalSection、Mutex、Semaphore 等等?
當然,筆者這里只列舉了這個軟件的主干部分,還有許多方方面面的細節(jié)需要考慮。這就需要讀者根據自己的情況,斟酌和篩選了。您想達到什么目的,您就去學習和研究相關的代碼。
總結起來,可以得到如下公式:
一款C++軟件 = C++語法 + 操作系統(tǒng)API函數(shù)調用
進階
如果您達到了我上面說的三點后,可以再找一些高質量的開源的項目去實戰(zhàn)一下。需要注意的是最好找一些沒有復雜業(yè)務或者您熟悉其業(yè)務的開源項目(如開源的 IM 系統(tǒng)),如果你不熟悉其業(yè)務,不僅要學習其業(yè)務(軟件功能),還需要再去學習它的源碼,最后可能讓我們迷失了最初學習這款軟件的目的。
學習這些項目的同時,讀者應該先確定自己的學習目的,如果您的目的是學習和借鑒這款軟件的架構,那么先從整體去把握,不要一開始就迷失在細枝末節(jié)中,這類我稱之為“粗讀”;或者,您的目的是學習下開源軟件的在一些細節(jié)上的處理與做法,這個時候,您可以針對性地去閱讀您感興趣的模塊,深入到每一行代碼上去。
學習開源軟件存在一種風氣,許多新手喜歡道聽途說,一聽別人說這個軟件不好,那個軟件存在某某瑕疵就放棄閱讀它的打算了。然后到了實際開發(fā)中,因為心中沒有任何已有軟件開發(fā)問題的解決方案,產生挫敗感,久而久之就對本來喜歡的 C/C++ 開發(fā)失去了興趣。學習的過程是先接觸,再熟悉,再模仿,再創(chuàng)造。不管什么開源項目,在您心中沒有任何思路或者解決方案時,您應該先接觸熟悉,不斷模仿,做到至少心中有一套對于某場景的解決方案,然后再來談創(chuàng)新談批判、改造別人的項目。
我個人學習一套陌生的開源項目時,總是喜歡將程序用調試器正常跑起來,然后再中斷下來,統(tǒng)計當前的線程數(shù)目,然后通過程序入口 main 函數(shù)從主線程追蹤其他工作線程是如何創(chuàng)建的;接著,分析和研究每個線程的用途以及線程之間交互的,這就是整體把握,接著找我感興趣的細節(jié)去學習。
這里我以學習 redis 為例,將 redis 源碼從官網下載下來以后,使用您喜歡的代碼閱讀器管理起來,我這里使用的是 Visual Studio,如下圖所示:
在大致了解了 redis 有哪些代碼模塊以后,我們把代碼拷貝到 linux 平臺,然后編譯并使用 gdb 調試器跑起來。如下圖所示:
然后使用按 ctrl + C 將gdb中斷下來,輸入info threads查看當前程序的所有線程:
接著挨個使用 thread + 線程編號 和 bt 命令去查看每個線程的上下文調用堆棧:
然后對照每個線程的上下文堆棧,搞清楚其邏輯,并結合主線程,看看每個線程是在何時啟動、端口在何時啟動偵聽的等等。等做完這一步,關于 redis-server 的框架也基本清楚了。
端口在何時啟動偵聽的等等。等做完這一步,關于 redis-server 的框架也基本清楚了。
最后,如果對 redis-server 源碼中各種數(shù)據結構和細節(jié)感興趣,我們可以進一步深入到具體的代碼細節(jié)。
當然,不熟悉 gdb 的讀者看筆者這段操作流程比較困難,這是正常的,說明如果想通過調試去研究 redis 這一款開源軟件,您需要去補充一點 gdb 調試的知識。這就是我上文中所說的,針對性地補缺補差。
小結
關于 C/C++,暫且就討論這么多。最后再強調一遍,C++ 是一門講究深度的語言,其“深度”不是體現(xiàn)在會多少 C++ 語法,而是能夠洞察您所寫的 C++ 代碼背后的系統(tǒng)原理,這是需要長期不斷的積累的,沒有速成之法。反過來一旦學成,可以快速地學習其他語言和框架。個人覺得,如果自主創(chuàng)業(yè)或者想在二三線城市長期發(fā)展的讀者,C/C++ 應該是優(yōu)選語言,有了它作為基礎,您可以跳出依賴各種環(huán)境和框架的窠臼,快速地學習和開發(fā)您想要的軟件,完成您想要的業(yè)務產品。
-
WINDOWS
+關注
關注
4文章
3547瀏覽量
88766 -
C語言
+關注
關注
180文章
7605瀏覽量
136930 -
C++
+關注
關注
22文章
2109瀏覽量
73677
發(fā)布評論請先 登錄
相關推薦
評論