1. 程式人生 > >大牛關於學習C++的建議

大牛關於學習C++的建議

在31年前(1979年),一名剛獲得博士學位的研究員,為了開發一個軟體專案發明了一門新程式語言,該研究員名為Bjarne Stroustrup,該門語言則命名為——C with classes,四年後改稱為C++。C++是一門通用程式語言,支援多種程式設計正規化,包括過程式、面向物件(object-oriented programming, OP)、泛型(generic programming, GP),後來為泛型而設計的模版,被發現及證明是圖靈完備的,因此使C++亦可支援模版超程式設計正規化(template metaprogramming, TMP)。C++繼承了C的特色,既為高階語言,又含低階語言功能,可同時作為系統和應用程式語言。

C++廣泛應用在不同領域,使用者以數百萬計。根據近十年的調查,C++的流行程度約穩定排行第3位(於C/Java之後)。 C++經歷長期的實踐和演化,才成為今日的樣貌。1998年,C++標準委員會排除萬難,使C++成為ISO標準(俗稱C++98),當中含非常強大的標準模版庫(standard template library, STL)。之後委員會在2005年提交了有關標準庫的第一個技術報告(簡稱TR1),併為下一個標準C++0x而努力。可惜C++0x並不能在200x年完成,各界希望新標準能於2011年內出臺。

流行的C++編譯器中,微軟Visual C++ 2010已實現部分C++0x語法並加入TR1擴充庫,而gcc對C++0x語法和庫的支援比VC2010更多。

應否選擇C++
哪些程式適宜使用C++?
C++並非萬能丹,我按經驗舉出一些C++的適用時機。

C++適合構造程式中需求較穩定的部分,需求變化較大的部分可使用指令碼語言;
程式須儘量發揮硬體的最高效能,且效能瓶頸在於CPU和記憶體;
程式須頻繁地與作業系統或硬體溝通;
程式必須使用C++框架/庫,如大部分遊戲引擎(如Unreal/Source)及中介軟體(如Havok/FMOD),雖然有些C++庫提供其他語言的繫結,但通常原生的API效能最好、最新;
專案中某個目標平臺只提供C++編譯器的支援。
按應用領域來說,C++適用於開發伺服器軟體、桌面應用、遊戲、實時系統、高效能運算、嵌入式系統等。

使用C++還是C?
C++和C的設計哲學並不一樣,兩者取捨不同,所以不同的程式設計師和軟體專案會有不同選擇,難以一概而論。與C++相比,C具備編譯速度快、容易學習、顯式描述程式細節、較少更新標準(後兩者也可同時視為缺點)等優點。在語言層面上,C++包含絕大部分C語言的功能(例外之一,C++沒有C99的變長陣列VLA),且提供OOP和GP的特性。但其實用C也可實現OOP思想,亦可利用巨集去實現某程度的GP,只不過C++的語法能較簡潔、自動地實現OOP/GP。C++的RAII(resource acquisition is initialization,資源獲取就是初始化)特性比較獨特,C/C#/Java沒有相應功能。回顧歷史,Stroustrup開發的早期C++編譯器Cpre/Cfront是把C++原始碼翻譯為C,再用C編譯器編譯的。由此可知,C++編寫的程式,都能用等效的C程式代替,但C++在語言層面上提供了OOP/GP語法、更嚴格的型別檢查系統、大量額外的語言特性(如異常、RTTI等),並且C++標準庫也較豐富。有時候C++的語法可使程式更簡潔,如運算子過載、隱式轉換。但另一方面,C語言的API通常比C++簡潔,能較容易供其他語言程式呼叫。因此,一些C++庫會提供C的API封裝,同時也可供C程式呼叫。相反,有時候也會把C的API封裝成C++形式,以支援RAII和其他C++庫整合等。

為何C++效能可優於其他語言?
相對運行於虛擬機器語言(如C#/Java),C/C++直接以靜態形式把源程式編譯為目標平臺的機器碼。一般而言,C/C++程式在編譯及連結時可進行的優化最豐富,啟動時的速度最快,執行時的額外記憶體開銷最少。而C/C++相對動態語言(如Python/Lua)也減少了執行時的動態型別檢測。此外,C/C++的執行行為是確定的,且不會有額外行為(例如C#/Java必然會初始化變數),也不會有如垃圾收集(GC)而造成的不確定性延遲,而且C/C++的資料結構在記憶體中的佈局也是確定的。有時C++的一些功能會使程式效能優於C,當中以內聯和模版最為突出,這兩項功能使C++標準庫的sort()通常比C標準庫的qsort()快多倍(C可用巨集或人手編碼去解決此問題)。另一方面,C/C++能直接對映機器碼,之間沒有另一層中間語言,因此可以做底層優化,例如使用內部(intrinsic)函式和嵌入組合語言。然而,許多C++的效能優點並非免費午餐,代價包括較長的編譯連結時間和較易出錯,因而增加開發時間和成本,這點稍後補充。

我進行了一個簡單全域性渲染效能測試(512x512畫素,每畫素10000個取樣),C++ 1小時36分、Java 3小時18分、Python約18天、Ruby約351天。評測方式和其他語言的結果詳見博文。

C++常見問題
C++原始碼跨平臺嗎?
C++有不錯的跨平臺能力,但由於直接對映硬體,因效能優化的關係,跨平臺能力不及Java及多數指令碼語言。然而,實踐跨平臺的C++軟體還是可行的,但須注意以下問題:

C++標準沒有規定原始資料型別(如int)的大小,需要特定大小的型別時,可自訂型別(如int32_t),同時對任何型別使用sizeof()而不假設其大小;
位元組序(byte order)按CPU有所不同,特別要注意二進位制輸入輸出、reinterpret_cast法;
原始資料和結構型別的地址對齊有差異;
編譯器提供的一些編譯器或平臺專用擴充指令;
避免作應用二進位制介面(application binary interface, ABI)的假設,例如呼叫函式時引數的取值順序在C/C++中沒定義,在C++中也不可隨便假設RTTI/虛表等實現方式。
總括而言,跨平臺C++軟體可在標頭檔案中用巨集檢測編譯器和平臺,再用巨集、typedef、自定平臺相關實現等方法去實踐跨平臺,C++標準不會提供這類幫助。

C++程式容易崩潰?
和許多語言相比,C/C++提供不安全的功能以最優化效能,有可能造成崩潰。但要注意,很多執行時錯誤,如向空指標/引用解引用、陣列越界、堆疊溢位等,其他語言也會報錯或丟擲異常,這些都是程式問題,而不是語言本身的問題。有些意見認為,出現這類執行時錯誤,應該儘量寫入日誌並立即崩潰,不該讓程式繼續執行,以免造成更大的影響(例如程式繼續把記憶體中錯誤的資料覆寫檔案)。若要容錯,可按業務把程式分割為多程序,像Chrome或使用fork()的形式。然而,C++有許多機制可以減少錯誤,例如以string代替C字串;以vector或array(TR1)代替原始陣列(有些實現可在除錯模式檢測越界);使用智慧指標也能減少一些原始指標的問題。另外,我最常遇到的Bug,就是沒有初始化成員變數,有時會導致崩潰,而且除錯版和發行版的行為可能不同。

C++要手動做記憶體管理?
C++同時提供在堆疊上的自動區域性變數,以及從自由儲存(free store)分配的物件。對於後者,程式設計師需手動釋放,或使用不同的容器和智慧指標。 C++程式設計師經常進一步優化記憶體,自定義記憶體分配策略以提升效能,例如使用物件池、自定義的單向/雙向堆疊區等。雖然C++0x還沒加入GC功能,但也可以自行編寫或使用現成庫。此外,C/C++也可以直接使用作業系統提供的記憶體相關功能,例如記憶體對映檔案、共享記憶體等。

使用C++常要重造輪子?
我曾參與的C++專案,都會重造不少標準庫已提供的功能,此情況在其他語言中較少出現。我試圖分析箇中原因。首先,C++標準庫相對很多語言來說是貧乏的,各開發者便會重複地製造自訂庫。從另一個角度看,C++標準庫是用C++編寫的(很多其他語言不用自身而是用C/C++去編寫庫),在能力和效能上,自訂庫和標準庫並無本質差別;另外,標準庫為通用而設,對不同平臺及多種使用需求作取捨,效能上有所影響,例如EA公司就曾發表自制的EASTL規格,描述遊戲開發方面對STL的效能及功能需求的特點;此外,多個C++庫一起使用,經常會因規範不同而引起衝突,又或功能重疊,所以專案可能須自行開發,或引入其他庫的概念或實現(如Boost/TR1/Loki),改寫以符合專案規範。

C++編譯速度很慢?
錯,是非常慢。我認為C++可能是實用程式語言中編譯速度最慢的。此問題涉及C++沿用C的編譯連結方式,又加入了複雜的類/泛型宣告和內聯機制,使編譯時間倍增。在C++對編譯方法改革之前(如module提案),可使用以下技巧改善:第一,使用pimpl手法,因效能損耗應用於呼叫次數不多的類;第二,僅包含必要標頭檔案,並儘量使用及提供前置宣告版本的標頭檔案(如iosfwd);第三採用基於介面的設計,但須注意虛擬函式呼叫成本;第四,採用unity build,即把多個cpp檔案結合在一個編譯單元進行編譯;第五,採用分散式生成系統如IncrediBuild。

C++缺乏什麼功能?
雖然C++已經非常複雜,但仍缺少很多常見功能。 C++0x作出了不少改善,例如語言方面加入Lambda函式、閉包、型別推導宣告等,而庫方面則加入正則表示式、採用雜湊表的unordered_set/unordered_map、引用計數智慧指標shared_ptr/weak_ptr等。但最值得留意的是C++0x引入多執行緒的語法和庫功能,這是C++演進的一大步。然而,模組、GC、反射機制等功能雖有提案,卻未加進C++0x。

C++使用建議
為應用挑選特性集
我同意Stroustrup關於使用C++各種技術的迴應:“你可以做,不意味著你必須這麼做。(Just because you can do it, doesn't mean that you have to.)” C++充滿豐富的特性,但同時帶來不同問題,例如過分複雜、編譯及執行效能的損耗。一般可考慮是否使用多重繼承、異常、RTTI,並調節使用模版及模版超程式設計的程度。使用過分複雜的設計和功能,可能會令部分團隊成員更難理解和維護。

為團隊建立程式設計規範
C++的編碼自由度很高,容易編寫風格迥異的程式碼,C++本身也沒有定義一些標準規範。而且,C++的原始檔物理構成,較許多語言複雜。因此,除了決定特性集,每個團隊應建立一套程式設計規範,包括原始檔格式(可使用檔案模版)、花括號風格。

儘量使用C++風格而非C風格
由於C++有對C相容的包袱,一些功能可以使用C風格實現,但最好使用C++提供的新功能。最基本的是儘量以具名常量、行內函數和泛型取代巨集,只把巨集用在條件式編譯及特殊情況。舊式的C要求區域性變數宣告在作用域開端,C++則無此限制,應把變數宣告儘量置於鄰近其使用的地方,for()的迴圈變數宣告可置於for的括號內。 C++中能加強型別安全的功能應儘量使用,例如避免“萬能”指標void *,而使用個別或泛型型別;用bool而非int表示布林值;選用4種C++ cast關鍵字代替簡單的強制轉換。

結合其他語言
如前文所述,C++並非適合所有應用情境,有時可以混合其他語言使用,包括用C++擴充套件其他語言,或在C++程式中嵌入指令碼語言引擎。對於後者,除了使用各種指令碼語言的專門API,還可使用Boost或SWIG作整合。

C++學習建議
C++缺點之一,是相對許多語言複雜,而且難學難精。許多人說學習C語言只需一本K&R《C程式設計語言》即可,但C++書籍卻是多不勝數。我是從C進入C++,皆是靠閱讀自學。在此分享一點學習心得。個人認為,學習C++可分為4個層次:

第一層次,C++基礎:挑選一本入門書籍,如《C++ Primer》、《C++大學教程》、或Stroustrup撰寫的經典《C++程式設計語言》或他一年半前的新作《C++程式設計原理與實踐》,而一般C++課程也止於此,另外《C++ 標準程式庫》及《The C++ Standard Library Extensions》可供參考;
第二層次,正確高效地使用C++:此層次開始必須自修,閱讀過《(More)Effective C++》、《(More)Exceptional C++》、《Effective STL》及《C++程式設計規範》等,才適宜踏入專業C++開發之路;
第三層次,深入瞭解C++:關於全域性問題可讀《深入探索C++物件模型》、《Imperfect C++》、《C++沉思錄》、《STL原始碼剖析》,要挑戰智商,可看關於模版及模版超程式設計的書籍如《C++ Templates》、《C++設計新思維》、《C++模版超程式設計》;
第四層次,研究C++:閱讀《C++語言的設計和演化》、《程式設計的本質》(含STL設計背後的數學根基)、C++標準檔案《ISO/IEC 14882:2003》、C++標準委員會的提案書和報告書、關於C++的學術文獻。
由於我主要是應用C++,大約只停留於第二、三個層次。然而,C++只是軟體開發的一環而已,單憑語言並不能應付業務和工程上的問題。建議讀者不要強求幾年內“徹底學會C++的知識”,到達第二層左右便從工作實戰中汲取經驗,有興趣才慢慢繼續學習更高層次的知識。雖然學習C++有難度,但也是相當有趣且有滿足感的。

數十年來,C++雖有起伏,但她依靠其使用者而不斷得到頑強的生命力,相信在我退休之前都不會與她分離,也希望更進一步瞭解她,與她走進未來。

--------------------- 
作者:守望者在這裡 
來源:CSDN 
原文:https://blog.csdn.net/lxw907304340/article/details/50441179 
版權宣告:本文為博主原創文章,轉載請附上博文連結!