◆ 概念介紹ios
繼承:爲了代碼的重用,保留基類的本來結構,並新增派生類的部分,同時可能覆蓋(overide)基類的某些成員。ide
多態:一種將不一樣的特殊行爲和單個泛化記號相關聯的能力,分爲靜態多態和動態多態。函數
◆ 繼承:spa
一個派生類能夠經過繼承得到基類的全部成員,而無需再次定義它們。分爲public、protected和private三種繼承方式,前兩種方式保持基類的全部成員的屬性不變,且派生類能夠訪問基類的public和protected成員,但仍然不能訪問基類的private成員;private繼承將使得基類的全部成員在派生類中表現爲private屬性。3d
聲明一個派生類對象,即在構造派生類對象時,遵循基類的接口,先構造基類子對象,再構造派生類增長的部分。其中的組成由下圖所示:指針
當出現菱形繼承時,例以下圖所示:code
要構造一個SleepSofa對象,就要構造一個Sofa和一個Bed子對象,這其中又同時構造了兩次Furniture對象,這是不合理的。所以Bed和Sofa類要對Furniture類進行虛繼承(virtual public Furniture)來避免這種情況。orm
◆ 多態:對象
靜態多態:在編譯時期就已經肯定了的行爲,例如帶變量的宏,模板,函數重載,運算符重載,拷貝構造等。blog
動態多態:在運行時期才能肯定調用的行爲。例如虛函數調用機制。本部分主要討論的是動態多態。虛函數是實現動態多態的機制,其核心理念就是經過基類指針來訪問派生類定義的成員。成員函數在基類爲虛函數時,在派生類一樣也是虛函數。純虛函數是指不但願基類對象調用的成員函數,須要派生類覆蓋實現這樣的純虛函數。(注:若是某個成員函數在基類中沒有用virtual關鍵字修飾,即普通函數,而在派生類中卻又有徹底相同的成員函數聲明,兩個函數即便有相同的名字和相同的參數類型與數量,這兩個函數也是徹底不一樣的函數,由於類的做用域不一樣)
虛函數表(vtable):每一個類都擁有一個虛函數表,虛函數表中羅列了該類中全部虛函數的地址,排列順序按聲明順序排列,例如這樣兩個類
class Base { virtual void f() {} virtual void g() {} //其餘成員 };
Base b;
class Derive : public Base { void f() {} virtual void d() {} //其餘成員 }; Derive d;
虛表指針(vptr):每一個類有一個虛表指針,當利用一個基類的指針綁定基類或者派生類對象時,程序運行時調用某個虛函數成員,會根據對象的類型去初始化虛指針,從而虛表指針會從正確的虛函數表中尋找對應的函數進行動態綁定,所以能夠達到從基類指針調用派生類成員的效果。
那麼爲何須要虛指針和虛函數表來實現動態多態呢?由於不管是什麼函數,包括類內的虛函數和非虛函數,都會儲存在內存中的代碼段。可是當編譯器在編譯時,就能夠肯定普通函數和非虛函數的入口地址,以及其調用的信息,因此這指的是常量指針。當遇到動態多態時,虛函數真正的入口地址的指針要在運行時根據對象的類型才能肯定,因此要經過虛指針從虛函數表中找虛函數對應的入口地址。
固然,用基類指針綁定的子類對象,只能經過這個基類指針調用基類中的成員,由於做用域僅限於基類的子對象,子類新增的部分是看不見的。
總結爲下面這個例程:
#include <iostream> using std::cout; using std::endl; class Base { public: void fun() { cout << "Base::fun()" << endl; } virtual void vfun() { cout << "Base::virtual fun()" << endl; } }; class Derive : public Base { public: void fun() { cout << "Derive::fun()" << endl; } virtual void vfun() { cout << "Derive::virtual fun()" << endl; } void dfun() { cout << "Derive::dfun()" << endl; } }; int main() { Base* bp = new Base(); Base* dp = new Derive(); bp->fun(); bp->vfun(); dp->fun(); dp->vfun(); //dp->dfun(); //編譯錯誤:基類指針指向子類中基類的子對象 //不能看到子類的成員 delete bp; delete dp; return 0; }
輸出爲:
能夠看出,bp綁定一個基類對象,調用本身的成員無異議;dp綁定的是一個子類對象,所以調用fun()時,因爲dp是一個基類指針,做用域在於基類中,因此調用的是基類的fun(),而調用vfun()是經過動態綁定調用虛函數表中被子類覆蓋的Derive::vfun(),而若是要調用dfun()時則會出現編譯錯誤,由於子類獨有成員基類指針不可見。
注:在解有關動態多態的題時,只要把握住一點:這個指針指向的究竟是基類對象仍是子類對象,若是是基類對象,則調用基類的成員函數,若是是子類對象,則要考慮到這個虛成員函數是否被子類中的成員覆蓋掉,便是否產生了動態綁定。另外還有一點,從子類對象強制類型轉換爲基類對象是容許的,而相反地要從基類對象強制轉換成子類對象是錯誤的(編譯不經過)。
Base* dp1 = new Derive(); Derive* dp2 = (Derive*) dp1; //基類指針指向的是子類對象,能夠強制轉化爲子類指針 Base* bp1 = new Base(); Derive* bp2 = (Base*) bp1; //錯誤,[Error] invalid conversion from 'Base*' to 'Derive*' [-fpermissive] //基類指針指向的是基類對象,不能強制轉化爲子類指針