靜态多态
我們以前說過的函數重載就是一個簡單的靜态多态
int Add(int left, int right){ return left + right;}double Add(double left, int right){ return left + right;}int main(){ Add(10, 20); //Add(10.0, 20.0); //這是一個問題代碼 Add(10.0,20); //正常代碼 return 0;}
可以看出來,靜态多态是編譯器在編譯期間完成的,編譯器會根據實參類型來選擇調用合适的函數,如果有合适的函數可以調用就調,沒有的話就會發出警告或者報錯。。。比較簡單,不做多介紹。
動态多态
什麼是動态多态呢? 動态多态: 顯然這和靜态多态是一組反義詞,它是在程式運作時根據基類的引用(指針)指向的對象來确定自己具體該調用哪一個類的虛函數。 我在臨潼上學,我就以這邊的公共汽車舉個栗子啊:
class TakeBus{public: void TakeBusToSubway() { cout << "go to Subway--->please take bus of 318" << endl; } void TakeBusToStation() { cout << "go to Station--->pelase Take Bus of 306 or 915" << endl; }};//知道了去哪要做什麼車可不行,我們還得知道有沒有這個車class Bus{public: virtual void TakeBusToSomewhere(TakeBus& tb) = 0; //???為什麼要等于0};class Subway:public Bus{public: virtual void TakeBusToSomewhere(TakeBus& tb) { tb.TakeBusToSubway(); }};class Station :public Bus{public: virtual void TakeBusToSomewhere(TakeBus& tb) { tb.TakeBusToStation(); }};int main(){ TakeBus tb; Bus* b = NULL; //假設有十輛公共汽車,如果是奇數就是去地鐵口的,反之就是去火車站的 for (int i = 1; i <= 10; ++i) { if ((rand() % i) & 1) b = new Subway; else b = new Station; } b->TakeBusToSomewhere(tb); delete b; return 0;}
這就是一個簡單的動态多态的例子,它是在程式運作時根據條件去選擇調用哪一個函數。 而且,從上面的例子我們還發現了我在每一個函數前都加了virtual這個虛拟關鍵字,想想為什麼?如果不加會不會構成多态呢? 幹想不如上機實踐:
class Base{public: virtual void Funtest1(int i) { cout << "Base::Funtest1()" << endl; } void Funtest2(int i) { cout << "Base::Funtest2()" << endl; }};class Drived :public Base{ virtual void Funtest1(int i) { cout << "Drived::Fubtest1()" << endl; } virtual void Funtest2(int i) { cout << "Drived::Fubtest2()" << endl; } void Funtest2(int i) { cout << "Drived::Fubtest2()" << endl; }};void TestVirtual(Base& b){ b.Funtest1(1); b.Funtest2(2);}int main(){ Base b; Drived d; TestVirtual(b); TestVirtual(d); return 0;}
在調用FuncTest2的時候我們看出來它并沒有給我們調用派生類的函數,是以我們可以對動态多态的實作做個總結。
動态多态的條件: ●基類中必須包含虛函數,并且派生類中一定要對基類中的虛函數進行重寫。 ●通過基類對象的指針或者引用調用虛函數。
重寫 : (a)基類中将被重寫的函數必須為虛函數(上面的檢測用例已經證明過了) (b)基類和派生類中虛函數的原型必須保持一緻(傳回值類型,函數名稱以及參數清單),協變和析構函數(基類和派生類的析構函數是不一樣的)除外 (c)通路限定符可以不同
**那麼問題又來了,什麼是協變? ** 協變:基類(或者派生類)的虛函數傳回基類(派生類)的指針(引用)
總結一道面試題:那些函數不能定義為虛函數? 經檢驗下面的幾個函數都不能定義為虛函數: 1)友元函數,它不是類的成員函數 2)全局函數 3)靜态成員函數,它沒有this指針 3)構造函數,拷貝構造函數,以及指派運算符重載(可以但是一般不建議作為虛函數)
**抽象類: ** 在前面公共汽車的例子上我提了一個問題:
class Bus{public: virtual void TakeBusToSomewhere(TakeBus& tb) = 0; //???為什麼要等于0};
在成員函數(必須為虛函數)的形參清單後面寫上=0,則成員函數為純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能執行個體化出對象。純虛函數在派生類中重新定義以後,派生類才能執行個體化出對象。純虛函數是一定要被繼承的,否則它存在沒有任何意義。
多态調用原理
class Base{public: virtual void Funtest1(int i) { cout << "Base::Funtest1()" << endl; } virtual void Funtest2(int i) { cout << "Base::Funtest2()" << endl; } int _data;};int main(){ cout << sizeof(Base) << endl; Base b; b._data = 10; return 0;}
8?不知道大家有沒有問題,反正我是有疑惑了。以前在對象模型(https://blog.csdn.net/qq_39412582/article/details/80808754)時我提到過怎麼來求一個類的大小。按照那個方法,這裡應該是4才對啊,為什麼會是8呢?
通過觀察。我們發現這個例子裡面和以前不一樣,類成員函數變成了虛函數,這是不是引起類大小變化的原因呢? 我們假設就是這樣,然後看看記憶體裡是怎麼存儲的呢?
可以看到它在記憶體裡多了四個位元組,那這四個位元組的内容到底是什麼呢?
是不是有點看不懂,我們假設它是一個位址去看位址裡存的東西的時候發現它存的是兩個位址。我假設它是虛函數的位址,我們來驗證一下:
typedef void (__stdcall *PVFT)(); //函數指針int main(){ cout << sizeof(Base) << endl; Base b; b._data = 10; PVFT* pVFT = (PVFT*)(*((int*)&b)); while (*pVFT) { (*pVFT)(); pVFT+=1; } return 0;}
結果好像和我們的猜想一樣,是一件值得開心的事。然後我給一張圖總結一下:
在反彙編中我們還可以看到,如果含有虛函數的類中沒有定義構造函數,編譯器會自動合成一個構造函數
派生類虛表: 1.先将基類的虛表中的内容拷貝一份 2.如果派生類對基類中的虛函數進行重寫,使用派生類的虛函數替換相同偏移量位置的基類虛函數 3.如果派生類中新增加自己的虛函數,按照其在派生類中的聲明次序。