更新時間:2017-11-14 來源:黑馬程序員 瀏覽量:
C++的復雜性始終是一個不可回避的現實。C++中有大量的陷阱和缺陷,后者導致了數目驚人的慣用法和workarounds。不加選擇的全盤預先學習,是非常糟糕的做法,不僅低效,而且根本沒有必要,實在是浪費生命。愛因斯坦曾經說過,“我只想知道‘他’(宇宙)的設計理念,其它的都是細節”。然而,正如另一些讀者指出的,如果對C++中的這些細節事先一點都沒有概念的話,那么實際編碼中一旦遇到恐怕就變成沒頭蒼蠅了,也許到哪里去RTFM都不知道。這也是為什么那么多C++面試都會不厭其煩地問一些有代表性的語言細節的原因。
把細節全盤裝在腦子里固然不好,但對細節一無所知同樣也不是個辦法。那么對于C++程序員來說,在學習中究竟應該以怎樣的態度和學習方法來對付C++的復雜性呢?其實答案也非常簡單,首先有一些很重要&必須的語言細節&特性是需要掌握的,然后我們只需知道在C++中大抵有哪些地方有復雜性(陷阱、缺陷),那么遇到問題的時候自然能夠知道到哪兒去尋找答案了。
C++的復雜性分類
這里并不詳細羅列C++的復雜性,而是提供一個分類標準。C++的復雜性有兩種分類辦法,一是分為非本質復雜性和本質復雜性;其中非本質復雜性分為缺陷和陷阱兩類。另一種分類辦法是按照場景分類:庫開發場景下的復雜性和日常編碼的復雜性。從從事日常編碼的實踐者的角度來說,采用后一種分類可以讓我們迅速掌握80%場景下的復雜性。
二八法則
以下通過列舉一些常見的例子來解釋這種分類標準:
80%場景下的復雜性:
1. 資源管理(C++日常復雜性的最主要來源):深拷貝&淺拷貝;類的四個特殊成員函數;使用STL;RAII慣用法;智能指針等等。
2. 對象生命期:局部&全局對象生存期;臨時對象銷毀;對象構造&析構順序等等。
3. 多態
4. 重載決議
5. 異常(除非你不用異常):棧開解(stack-unwinding)的過程;什么時候拋出異常;在什么抽象層面上拋出異常等等。
6. undefined&unspecified&implementation defined三種行為的區別:i++ + ++i是undefined behavior(未定義行為——即“有問題的,壞的行為,理論上什么事情都可能發生”);參數的求值順序是unspecified(未指定的——即“你不能依賴某個特定順序,但其行為是良好定義的”);當一個double轉換至一個float時,如果double變量的值不能精確表達在一個float中,那么選取下一個接近的離散值還是上一個接近的離散值是implementation defined(實現定義的——即“你可以在實現商的編譯器文檔中找到說明”)。這些問題會影響到你編寫可移植的代碼。
(注:以上只是一個不完全列表,用于演示該分類標準的意義——實際上,如果我們只考慮“80%場景下的復雜性”,記憶和學習的負擔便會大大減小。)
20%場景下的復雜性:
1. 對象內存布局
2. 模板:偏特化;非類型模板參數;模板參數推導規則;實例化;二段式名字查找;元編程等等。
3. 名字查找&綁定規則
4. 各種缺陷以及缺陷衍生的workarounds(C++書中把這些叫做“技術”):不支持concepts(boost.concept_check庫);類型透明的typedef(true-typedef慣用法);弱類型的枚舉(強枚舉慣用法);隱式bool轉換(safe-bool慣用法);自定義類型不支持初始化列表(boost.assign庫);孱弱的元編程支持(type-traits慣用法;tag-dispatch慣用法;boost.enable_if庫;boost.static_assert庫);右值缺陷(loki.mojo庫);不支持可變數目的模板參數列表(type-list慣用法);不支持native的alignment指定。
(注:以上只是一個不完全列表。你會發現,這些細節或技術在日常編程中極少用到,尤其是各種語言缺陷衍生出來的workarounds,構成了一個巨大的長尾,在無論是C++的書還是文獻中都占有了很大的比重,作者們稱它們為技術,然而實際上這些“技術”絕大多數只在庫開發當中需要用到。)
非本質復雜性&本質復雜性
此外,考慮另一種分類辦法也是有幫助的,即分為非本質復雜性和本質復雜性。
非本質復雜性(不完全列表)
1. 缺陷(指能夠克服的問題,但解決方案很笨拙;C++的書里面把克服缺陷的workarounds稱作技術,我覺得非常誤導)。
2. 陷阱(指無法克服的問題,只能小心繞過;如果跌進去,那就意味著你不知道這個陷阱,那么很大可能性你也不知道從哪去解決這個問題):一般來說,作為一個合格的程序員(不管是不是C++程序員),80%場景下的語言陷阱是需要記住才行的。比如深拷貝&淺拷貝;基類的析構函數應當為虛;缺省生成的類成員函數;求值順序&序列點;類成員初始化順序&聲明順序;導致不可移植代碼的實現相關問題等。
本質復雜性(不完全列表)
1. 內存管理
2. 對象生命期
3. 重載決議
4. 名字查找
5. 模板參數推導規則
6. 異常
7. OO(動態)和GP(靜態)兩種范式的應用場景和交互
總而言之,該文的目的是要告訴你從一個較高的層次去把握C++中的復雜性。其中最重要的一個指導思想就是在學習的過程中注意你正學習的技術或細節到底是80%場景下的還是20%場景下的,如果是20%場景下的(有大量這類復雜性,其中尤數各種各樣的workarounds為巨),那么也許最好的做法是只記住一個大概,不去作任何深究。此外,一般來說,不管使用哪門語言,認識語言陷阱對于編程來說都是一個必要的條件,語言陷阱的特點是如果你掉進去了,那么很大可能意味著你本來就不知道這有個陷阱,后者很大可能意味著你不知道如何解決。友情提示:獲得更多學科學習視頻+資料+源碼,請加QQ:3276250747。
本文版權歸黑馬程序員C/C++學院所有,歡迎轉載,轉載請注明作者出處。謝謝!
作者:黑馬程序員C/C++培訓學院
首發:http://c.itheima.com/