多態:顧名思義,多態就是多種形態,也就是對不同對象發送同一個消息,不同對象會做出不同的響應。
并且多態分為靜態多態和動態多態。
靜態多態就是在系統編譯期間就可以確定程序執行到這里將要執行哪個函數,例如:函數的重載,對象名加點操作符執行成員函數等,都是靜態多態,其中,重載是在形成符號表的時候,對函數名做了區分,從而確定了程序執行到這里將要執行哪個函數,對象名加點操作符執行成員函數是通過this指針來調用的。
函數的重載比較簡單,不再贅述,這里我們通過一個簡單的例子來看一下對象名加點操作符執行成員函數的靜態多態:
class A{public: void Set(int a) { _a = a; }public: int _a;};int main(){ A a1; a1.Set(15); return 0;}這里定義了一個A類,有一個成員函數和一個成員,我們將程序的部分匯編代碼截取出來如下圖: 我們可以看到這里直接是一個lea指令將a1對象的地址放入寄存器eax中,也就是對象的this指針,然后用call指令就可以跳轉到Set函數,也就是說其匯編代碼在此時就知道應該要去到哪個地方之行哪個函數,這就是靜態多態,也叫編譯時多態。
動態多態則是利用虛函數實現了運行時的多態,也就是說在系統編譯的時候并不知道程序將要調用哪一個函數,只有在運行到這里的時候才能確定接下來會跳轉到哪一個函數的棧幀。
在說動態多態之前我們先來看一下什么是虛函數,虛函數就是在基類中聲明該函數是虛擬的(在函數之前加virtual關鍵字),然后在子類中正式的定義(子類中的該函數的函數名,返回值,函數參數個數,參數類型,全都與基類的所聲明的虛函數相同,此時才能稱為重寫,才符合虛函數,否則就是函數的重載),再定義一個指向基類對象的指針,然后使該指針指向由該基類派生的子類對象,再然后用這個指針來調用改虛函數,就能實現動態多態。
下面我們通過一個例子來看一下利用虛函數實現的動態多態:
class A{public: A(int a = 10) :_a(a) {} virtual void Get() { cout << "A:: _a=" << _a << endl; }public: int _a;};class B : public A{public: B(int b = 20) :_b(b) {} void Get() { cout << "B:: _b=" << _b << endl; } int _b;};int main(){ A a1; B b1; A* ptr1 = &a1; ptr1->Get(); ptr1 = &b1; ptr1->Get(); return 0;}在這里我們看到,基類A的Get函數聲明為虛函數,在B類中進行了重寫, 然后在main函數中分別用基類的ptr1和指向子類的ptr2進行調用虛函數Get,我們得到了如下圖的輸出: 這說明確實是實現了不同調用,而且是在運行時,那么虛函數的底層到底是怎么實現的呢,我們來看一下匯編代碼及其對象模型:
通過上圖的匯編代碼,我們看到這里做了一系列的指針解引用處理,最后確定了eax中應該存放的this指針的值,要搞清楚這個必須要搞清楚子類的對象模型。
用監視窗口查看b1可以看到如上圖所示,這里的_vfptr是一個虛表指針,它指向一個存放該類對象的所有虛函數的地址的表,我們可以將該表理解為一個函數指針數組,在該數組的最后一個元素,編譯系統會將其置為0,。 對象模型如下圖示:
其中紅色為A類的成員,黑色為B類對象b1的成員,紫色就是一個虛函數表,存放著存放該類對象的所有虛函數的地址,匯編代碼做了一系列的指針解引用處理就是為了從虛函數表中找到相應的虛函數進行調用,從而實現了動態多態。
新聞熱點
疑難解答
圖片精選