今天起,開始我的C++務實之旅。務實,就是少浪費時間,C++這門難纏的語言不值得浪費我們太多的生命。
務實,就要觀其大略,莫糾纏於細節。
務實,就是用20%的時間學到80%有用的知識。
本系列以專題劃分,各個專題並非一蹴而就,而是隨學習的深入不斷增添新內容,以備用時參考。
本系列側重於“能做什麼”而非“不能做什麼”,側重於“應該怎樣做”而非“不該怎樣做”。
如果您想通過本系列學習,最好具備以下條件:
1 學過C++
2 即將或正在使用C++編程
OK. Go!
下面是從一個好心腸的高手那裡抄來的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為巨),那麼也許最好的做法是只記住一個大概,不去作任何深究。此外,一般來說,不管使用哪門語言,認識語言陷阱對於編程來說都是一個必要的條件,語言陷阱的特點是如果你掉進去了,那麼很大可能意味著你本來就不知道這有個陷阱,後者很大可能意味著你不知道如何解決。