C++中虛函數功能的實現機制ios
要理解C++中虛函數是如何工做的,須要回答四個問題。函數
一、 什麼是虛函數。測試
虛函數因爲必須是在類中聲明的函數,所以又稱爲虛方法。全部以virtual修飾符開始的成員函數都成爲虛方法。此時注意是virtual修飾的成員函數不是virtual修飾的成員函數名。編碼
例如:基類中定義:spa
virtual void show(); //因爲有virtual修飾所以是虛函數.net
voidshow(int); //雖然和前面聲明的show虛函數同名,但不是虛函數。指針
全部的虛函數地址都會放在所屬類的虛函數表vtbl中。另外在基類中聲明爲虛函數的成員方法,到達子類時仍然是虛函數,即便子類中從新定義基類虛函數時未使用virtual修飾,該函數地址仍會放在子類的虛函數表vtbl中。code
二、 正確區分重載、重寫和隱藏。對象
注意三個概念的適用範圍:處在同一個類中的函數纔會出現重載。處在父類和子類中的函數纔會出現重寫和隱藏。blog
重載:同一類中,函數名相同,但參數列表不一樣。
重寫:父子類中,函數名相同,參數列表相同,且有virtual修飾。
隱藏:父子類中,函數名相同,參數列表相同,但沒有virtual修飾;
或:函數名相同,參數列表不一樣,不管有無virtual修飾都是隱藏。
例如:
基類中:(1) virtual void show(); //是虛函數
(2) void show(int); //不是虛函數
子類中:(3) void show(); //是虛函數
(4) void show(int); //不是虛函數
1,2構成重載,3,4構成重載,1,3構成重寫,2,4構成隱藏。另外2,3也會構成隱藏,子類對象沒法訪問基類的void show(int)成員方法,可是因爲子類中4的存在致使了子類對象也能夠直接調用void show(int)函數,不過此時調用的函數不在是基類中定義的void show(int)函數2,而是子類中的與3重載的4號函數。
三、 虛函數表是如何建立和繼承的。
基類的虛函數表的建立:首先在基類聲明中找到全部的虛函數,按照其聲明順序,編碼0,1,2,3,4……,而後按照此聲明順序爲基類建立一個虛函數表,其內容就是指向這些虛函數的函數指針,按照虛函數聲明的順序將這些虛函數的地址填入虛函數表中。例如若show放在虛函數聲明的第二位,則在虛函數表中也放在第二位。
對於子類的虛函數表:首先將基類的虛函數表複製到該子類的虛函數表中。若子類重寫了基類的虛函數show,則將子類的虛函數表中存放show的函數地址(未重寫前存放的是子類的show虛函數的函數地址)更新爲重寫後函數的函數指針。若子類增長了一些虛函數的聲明,則將這些虛函數的地址加到該類虛函數表的後面。
四、 虛函數表是如何訪問的。
當執行pBase->show()時,要觀察show在Base基類中聲明的是虛函數仍是非虛函數。若爲虛函數將使用動態聯編(使用虛函數表決定如何調用函數),若爲非虛函數則使用靜態聯編(根據調用指針pBase的類型來肯定調用哪一個類的成員函數)。此處假設show爲虛函數,首先:因爲檢查到pBase指針類型所指的類Base中show定義爲虛函數,所以找到pBase所指的對象(有多是Base類型也多是Extend類型。),訪問對象獲得該對象所屬類的虛函數表地址。其次:查找show在Base類中聲明的位置在Base類中全部虛函數聲明中的位序。而後到pBase所指對象的所屬類(有多是Extend哦,多態)的虛函數表中訪問該位序的函數指針,從而獲得要執行的函數。
例如:
基類Base::virtualvoid show(); (1)
子類Extend::virtualvoid show(); (2)
Externext;
Base*pBase=&ext;
pBase->show();
當執行pBase->show();時首先到Base中查看show(),發現其爲虛函數,而後訪問pBase指向的ext對象,在對象中獲得Extend類的虛函數表,在Base類聲明中找到show()聲明的位序0,訪問Extend類的虛函數表的位置0,獲得show的函數地址。注意若只有基類定義了virtual void show();而子類未重寫virtual void show();即上面的函數(2),則Extend虛函數表中的位序0中存放的地址仍然是Base類中定義的virtual void show()函數,而若Extend類中重寫了Base類中的virtual void show()方法,則Extend的虛函數表中位序0的函數地址將被更新爲Extend中新重寫的函數地址。從而調用pBase->show()時將產生多態的現象。
總結:當調用pBase->show();時,執行的步驟:
1, 判斷Base類中show是否爲虛函數。
2, 若不是虛函數則找到pBase所指向的對象所屬類Base。執行Base::show()。如果虛函數則執行步驟3.
3, 訪問pBase所指對象的虛函數表指針獲得pBase所指對象所在類的虛函數表。
4, 查找Base中show()在聲明時的位序爲x,到步驟3獲得的虛函數表中找到位序x,從而獲得要執行的show的函數地址。
5, 根據函數地址和Base中聲明的show的函數類型(形參和返回值)訪問地址所指向的函數。
以上爲虛函數的工做機制。
注意只有用virtual修飾的成員方法纔會放到虛函數表中去。
子類對父類函數的隱藏將致使沒法經過子類對象訪問基類的成員方法。
所以給出如下建議:
一、 若要在子類中從新定義父類的方法(有virtual爲重寫,無virtual爲隱藏),則應確保子類中的函數聲明和父類函數聲明中的形參徹底同樣。但返回值類型是基類引用/指針的成員函數在從新定義時能夠返回子類的引用/指針(返回值協變),這是因爲子類的對象能夠賦給基類引用/指針。
二、 若基類中聲明瞭函數的重載版本,則在派生類中從新定義時應該從新定義全部基類的重載版本。這是由於,從新定義一個函數,其餘的基類重載版本將被隱藏,致使子類沒法使用這些基類的成員方法。因此須要每一個都從新定義。若一些父類的重載版本,子類確實不須要修改,則因爲從新定義了一個重載版本,即便有些重載版本不須要修改也要從新定義,在定義體中直接調用基類的成員方法(使用做用於限定符訪問)。
三、 從虛函數的實現機制能夠看到要想在子類中實現多態須要知足三個重要的條件。(1)在基類中函數聲明爲虛函數。(2)在子類中,對基類的虛函數進行了重寫。(3)基類的指針指向了子類的對象。
上述轉載自:here
隱藏測試: 子類中只定義了show(),那麼子類對象將不能訪問父類的show(int)
#include<iostream> using namespace std; class Parent{ public: void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; c.show(); getchar(); return 0; }
Child::show()
多態 父類中沒有定義虛函數show,則使用靜態聯編,根據指針p的類型來決定調用哪一個類的函數
#include<iostream> using namespace std; class Parent{ public: void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Parent::show()
Parent::show()2
多態 父類中定義了虛函數show,查找show在Base類中聲明的位置在父類中全部虛函數聲明中的位序。而後找到p所指對象的所屬類的虛函數表中訪問該位序的函數指針,從而獲得要執行的函數。
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Child::show()
Parent::show()2
多態 若是子類中沒有從新父類方法,那麼複製父類的虛函數表
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; class GrandChild:public Child{ }; int main() { GrandChild c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Child::show()
Parent::show()2
多態 若是子類重寫了父類的虛函數,那麼經過p指針所在類的該虛函數的位序訪問子類中的對應的函數,若是子類重寫的是非虛函數,那麼經過指針p只能訪問p所在類的該函數
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } void show(int t) { cout<<"Child::show()"<<t<<endl; } }; class GrandChild:public Child{ void show() { cout<<"GrandChild::show()"<<endl; } void show(int t) { cout<<"GrandChild::show()"<<t<<endl; } }; int main() { GrandChild c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
GrandChild::show()
Parent::show()2