題外話1:浪費了兩天,可恥!
題外話2:你這個年紀,做得好是理所當然,做不好是罪孽深重!!! --- 深以為然。
題外話3:從開始看C++ Primer 到現在,整整24天了,沒想到基礎方面耗費這么久---主要是沒想到C++居然如此繁瑣。精勤求學,當持之以恆。
面向對象的三大特征:數據抽象、繼承、動態綁定。其實就是Java中說的封裝、繼承、多態,翻譯的不同而已。
多態這個詞用的不好,太抽象,動態綁定就清晰的多。
繼承語法
class B : public A { /*類B*/ };
上面最重要的就是“:”和public。前者記住就行,繼承的語法規定。后者是權限訪問符(我更喜歡叫它繼承權限),public、protected、private。
繼承權限和父類成員的訪問權限,共同決定了繼承的成員在子類中的訪問權限---繼承權限可以進一步限制,但不能放松---最小訪問控制。
例如父類的public成員a,繼承權限為private,則a在子類中就是private部分。
這里只有一個需要注意的點,如果省略繼承權限,那么 class 默認為private,struct默認為public。
補充:
//using聲明導入 class Derived : private Base { public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; // ... };
繼承的結構
子類對象的父類部分其實就是一個父類對象!這個認識很重要!
父類的引用或者指針,指向的就是子類對象的父類對象部分!!!
動態綁定
首先,這是一個運行期的概念,它出現的條件是:通過父類的引用或指針調用函數。
這點同Java的概念一樣。但有一點不同:動態綁定僅限於 virtual 函數!
就是說,普通的函數不會動態綁定!!!這個認識更重要!
普通函數的綁定是編譯器進行的,靜態綁定。
這里的關鍵點是,父類的引用或者指針是靜態類型,而子類對象(或者父類本身的對象)是在運行期才知道的。所以叫動態綁定。
就是說,對象的實際類型可能不同於父類的引用或指針類型,這是C++動態綁定的關鍵。
關於虛函數
不是重載,是重寫。
如果想調用某個父類的虛函數,需要使用作用域操作符,如 A::x(); --僅限於子類的成員函數中調用。
注意:虛函數的默認實參如果不一致,會導致問題。
繼承之初始化
隱式的,會調用父類的默認構造進行初始化。再初始化子類自身的成員。
顯式的,只能在子類的初始化列表中調用父類的構造進行初始化。
--- 注意,都是先初始化父類對象,且只能直接初始化直接父類。
繼承之復制控制
隱式的,會調用父類的復制控制操作父類成員。再調用子類的復制控制。
如果自定義了復制構造函數,應該顯式的調用父類的復制構造函數操作父類成員。
如果自定義了賦值操作符,應該顯式的調用父類的賦值操作符操作父類成員。
--- 注意,必須防止賦值操作符操作自身!
B &B::operator=(const B &rhs){ if(this != &rhs){ A::operator=(rhs); // 顯式調用父類的賦值操作符 /*其他*/ } }
繼承之析構函數
注意,子類析構不負責父類析構,而是由編譯器顯式的調用父類的析構函數!
需要將根類的析構函數設為虛函數!!
析構順序與構造順序相反。
純虛函數
在虛函數的參數列表后面加上 =0 ,即將該虛函數聲明為純虛函數。
具有該函數的類將無法創建對象。
--這個就和Java的抽象方法、抽象類一致了。
重要
在構造或者析構期間,總是先構造父類或者析構子類,這時類型是不完整的。
為了解決這個問題,編譯器將構造或者析構期間的對象看作父類對象!!!
所以,構造或者析構期間調用的虛函數只會是構造或者析構函數所在類的虛函數!不是子類的虛函數!!!
函數重載
子類的同名函數會覆蓋掉父類中所有的同名函數--哪怕是不同的函數原型!!!(務必注意,非 virtual)
認真的說,我不認為這是重載,不過書上這么叫,那就這么叫吧。
這種覆蓋用起來很不爽,因為完全覆蓋了所有的父類版本。所幸我們有比較好的解決辦法:使用using聲明。

1 #include <iostream> 2 3 using namespace std; 4 5 // 根類的析構函數,必須virtual。 6 // 子類的函數會覆蓋父類的同名函數。 7 // 如果想重載,使用using聲明即可。 8 class A 9 { 10 public: 11 A(){}; 12 virtual ~A(){}; 13 void hi(){ 14 cout<<"hi from A"<<endl; 15 } 16 void hi(string name){ 17 cout<<"hi "<<name<<" from A"<<endl; 18 } 19 }; 20 21 class B:public A 22 { 23 public: 24 B(){}; 25 ~B(){}; 26 using A::hi; // using 聲明 27 void hi(int num){ 28 cout<<"hi num["<<num<<"] from B, overloaded"<<endl; 29 } 30 31 }; 32 33 34 int main(int argc, char const *argv[]) 35 { 36 B b; 37 b.hi(); 38 b.hi("joe"); 39 b.hi(9527); 40 41 return 0; 42 }
繼承特性:
友元關系不能繼承。
靜態成員只會存在一個實例。
構造函數和復制控制不能繼承。
根類的析構函數應該設為虛函數。
構造函數和析構函數都只能調用所在類的函數,哪怕是虛函數---就是說構造或者析構期間沒有動態綁定。