完整閱讀C++ Primer Plus
系統重新學習C++語言部分,記錄重要但易被忽略的,關鍵但易被遺忘的。
使用類
1、不能過載的運算子
1 sizeof sizeof運算子 2 . 成員運算子 3 .* 成員指標運算子 4 :: 作用域解析運算子 5 ?: 條件運算子 6 typeid 一個RTTI運算子 7 const_cast 強制型別轉換運算子 8 dynamic_cast 強制型別轉換運算子 9 reinterpret_cast 強制型別轉換運算子 10 static_cast 強制型別轉換運算子
2、只能通過成員函式過載的運算子
1 = 賦值運算子 2 () 函式呼叫運算子 3 [] 下標運算子 4 -> 通過指標訪問類成員的運算子
3、關於類的型別轉換函式,C++11支援對其使用explicit關鍵字,使其無法進行隱式型別轉換。
4、對於定義了一個以上的轉換函式的類,編譯器在某些情況下(如將一個物件直接賦值給一個基本型別,或用cout輸出時)無法確定應該使用哪一個轉換函式(進行隱式型別轉換),因此將出現二義性錯誤,但只有一個轉換函式時,編譯器只能選擇這一個,因此不會出錯。
類和動態記憶體分配
5、將新物件顯示地初始化為現有物件時將呼叫拷貝建構函式,預設的拷貝建構函式將除靜態成員以外的所有成員按值賦值。
1 String a(b); 2 String a = b; 3 String a = String(b); 4 String * a = new String(b);
將已有的物件賦值給另一個已有的物件時,會呼叫賦值建構函式。
6、靜態成員函式不與特定的物件關聯,因此只能使用靜態資料成員(單例模式)。
7、對於使用定位new運算子建立的物件,應顯式地呼叫其解構函式,需要注意的是,在析構時,物件的析構順序應該與建立順序相反,因為晚建立的物件可能依賴於早建立的物件,另外,只有當所有物件被銷燬後,才能釋放儲存這些物件地緩衝區。
8、只有建構函式可以使用初始化列表語法,對於const類成員(C++11之前)和宣告為引用的類成員,必須使用這種語法,因為它們只能在被建立時初始化。
類繼承
9、 公有繼承是最常用的繼承方式,它建立一種is-a關係,即派生類物件也是一個基類物件,可以對基類物件執行的任何操作,也可以對派生類物件執行。公有繼承不建立has-a關係;公有繼承不建立is-like-a關係;公有繼承不建立is-implemented-as-a(作為……來實現)關係;公有繼承不建立uses-a關係。在C++中,完全可以使用公有繼承來實現has-a、is-implemented-as-a或use-a關係,然而這樣做通常會導致程式設計方面的問題,因此,還是堅持使用is-a關係吧。
10、 在基類的方法中使用關鍵字virtual可使該方法在基類已經所有派生類(包括從派生類派生出來的類)中是虛的,也就是說只要函式名相同,只需要在基類中宣告為虛擬函式,那它的派生類中,包括派生派生類中的這個函式都是虛擬函式,但為了可讀性,一般派生類中的虛擬函式也用virtual宣告。
11、如果重新定義繼承的方法,應確保與原來的原型完全相同,但如果返回型別是基類引用或指標,則可以修改為指向派生類的引用或指標,這種特性被稱為返回型別協變,因為允許返回型別隨類型別的變化而變化。
12、如果基類宣告被過載了,則應在派生類中重新定義所有的基類版本,如果只在派生類中只定義了一個版本,則另外的版本將被隱藏。
13、C++允許純虛擬函式有定義,但不能在類內定義,可以在實現檔案中定義。
14、當基類和派生類都為至少一個成員採用了動態記憶體分配時,派生類的解構函式,拷貝建構函式,賦值建構函式都必須使用相應的基類方法來處理基類元素。對於解構函式,這是自動完成的;對於拷貝建構函式,是通過初始化列表中呼叫積累的拷貝建構函式完成的;對於賦值建構函式是通過使用作用域解析運算子顯示地呼叫基類的賦值建構函式完成的。
15、當派生類的友元函式需要訪問基類中的非公有成員時,做法是在派生類的友元函式中將派生類的引用強制型別轉換為基類的引用。
C++中的程式碼重用
16、當類的初始化列表包含多個專案時,這些專案的初始化順序為宣告它們的順序,而不是他們在初始化列表中的順序,如果程式碼使用一個成員的值作為另一個成員初始化表示式的一部分時,初始化順序就需要引起注意。
17、在繼承時,private是預設值,因此忽略訪問限定符也將導致私有繼承。
18、在私有繼承時,訪問基類方法,需要使用類名加作用域解析運算子訪問;訪問基類物件(例如將基類物件當作返回值時),可以將派生類強制型別轉換為基類;訪問基類友元函式時,因為友元函式不屬於成員函式,因此不能顯式地限定函式名去訪問,可以通過顯式地轉換為基類來呼叫正確的函式。
19、通常,應該使用包含來建立has-a關係,如果新類需要訪問原有類的保護成員,或需要重新定義虛擬函式,則應使用私有繼承。
20、對於指向物件的類或引用中的隱式向上轉換,公有繼承直接支援,保護繼承只在派生類中支援,私有繼承不支援。
21、如果要使私有繼承的基類中的私有函式可以在派生類外訪問,可以宣告一個公有函式,再去呼叫基類的私有函式,另外還可以使用using宣告:
1 class A:private B 2 { 3 public: 4 using B:foo; // 只需要有函式簽名即可,所有過載版本都可以使用 5 }
另一種老式的方法是將基類方法名放在派生類的共有部分。
22、在多重繼承中,如果出現了菱形繼承,頂端的基類應該使用虛繼承,防止底端的派生類包含兩份基類,同時,在建構函式的初始化列表裡應該顯式地呼叫頂端基類的建構函式,對於虛基類必須這樣做,否則將使用虛基類的預設建構函式,並且此時的虛基類無法通過中間的派生類去完成構造,但對於非虛基類,這是非法的。
23、在混合使用虛基類和非虛基類,類通過多條虛途徑和非虛途徑繼承某個特定的基類時,該類將包含,一個表示所有的虛途徑即基類子物件和分別表示各條非虛途徑的多個基類子物件。
24、使用非虛基類時,二義性的規則很簡單,只要類從不同類那裡繼承了同名的成員,使用時沒有用類名限定,則一定會導致二義性,但虛基類時不一定會導致二義性,如果某個名稱優先於其他名稱,則使用它。優先的規則是,派生類中的名稱優先於直接或間接祖先類中的相同名稱。
25、有間接虛基類的派生類包含直接呼叫間接基類建構函式的建構函式,這對於間接非虛基類是非法的。
26、 在類别範本中,模板程式碼不能修改引數的值,也不能使用引數得地址,在例項化時,用作表示式引數的值必須是常量表示式。
27、使用關鍵字template並指出所需型別來宣告類時,編譯器將生成類宣告的顯式例項化,宣告必須位於模板定義所在的名稱空間中。
28、C++允許部分具體化:
1 template <class T1, class T2> class Pair{}; // 一般版本 2 template <class T1> class Pair<T1, int>{}; // 部分顯式具體化版本
template後的<>中是沒有被具體化的引數,如果指定所有的型別,template後的<>將為空,這會導致顯式具體化。
29、如果有多個版本可以選擇,編譯器將選擇具體化最高的版本,部分具體化特性使得能夠設定各種限制。
1 template<T> class Feeb{}; 2 template<T*> class Feeb{}; // 也可以為指標提供特殊版本來部分具體化 3 4 template<class T1, class T2, class T3> class Trio{}; //一般版本 5 template<class T1, class T2> class Trio<T1, T2, T2>{}; // 使用T2設定T3 6 template<class T1> class Trio<T1, T1*, T1*>{}; // 使用T1的指標來設定T2和T3
30、較老的編譯器不支援模板成員,而另一些編譯器支援模板成員,但是不支援在類外面的定義。如果支援類外定義,則必須通過作用域解析運算子指出是哪個類的成員,並且使用巢狀模板的宣告方式。
1 template<typename T> 2 template<typename V> // 巢狀方式
31、 對於模板類的非模板友元函式,它不是通過物件呼叫的,因為它不是成員函式,它可以訪問全域性物件,可以使用全域性指標訪問非全域性物件,可以建立自己的物件,可以訪問獨立於物件的模板類靜態資料成員。
32、在宣告模板類的約束模板友元函式(友元函式也是模板函式)時,需要在類中宣告具體化的友元函式,同時也需要在類外宣告並且給出友元函式的定義。
33、除了使用typedef對模板進行重新命名,C++11新增了別名。
1 template<typename T> 2 using arrtype = std::array<T,12>; 3 arrtype<int> days;
這種語法也適用於非模板,用於非模板時,它和typedef等價。