標籤:
大家知道虛函數是通過一張虛函數表來實現的。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,其內容真是反應實際的函數。這樣,在有虛函數的類的執行個體中,這個表分配在了這個執行個體的記憶體中,所以,當用父類的指標來操作一個子類的時候,這張虛函數表就顯得尤為重要了。它就像一個地圖一樣,指明了實際所應該調用的函數。
C++的標準規則中說到,編譯器必須保證虛函數表的指標存在於對象執行個體中最前面的位置(這樣是為了保證正確取到虛函數的位移量)。這意味著通過對象執行個體的地址得到這張虛函數表,然後可以遍曆其中的函數指標,並調用相應的函數。
#include <iostream>using namespace std;class Base{public: virtual void fun1(){cout<<"Base::fun1\n";} virtual void fun2(){cout<<"Base::fun2\n";} virtual void fun3(){cout<<"Base::fun3\n";}private: int num1; int num2;};typedef void (*Fun)(void);int main(){ Base b; Fun pFun; //通過指標分別調用了對象b的3個虛函數。 pFun = (Fun)* ( (int*)*(int*)(&b)+0 ); pFun(); pFun = (Fun)* ( (int*)*(int*)(&b)+1 ); pFun(); pFun = (Fun)* ( (int*)*(int*)(&b)+2 ); pFun(); return 0;}/*程式執行結果如下:Base::fun1Base::fun2Base::fun3Press <RETURN> to close this window...*/
程式中的Base對象b記憶體結構如下:
一個類會有多少張虛函數表呢?
對於一個單繼承的類,如果它有虛函數,則只有一張虛函數表。對於多重繼承的類,它可能有多張虛函數的表。
#include <iostream>using namespace std;class Base1{public: Base1(int num):num_1(num){} virtual void fun1(){cout<<"Base1::fun1 "<<num_1<<endl;} virtual void fun2(){cout<<"Base1::fun2 "<<num_1<<endl;} virtual void fun3(){cout<<"Base1::fun3 "<<num_1<<endl;}private: int num_1;};class Base2{public: Base2(int num):num_2(num){} virtual void fun1(){cout<<"Base2::fun1 "<<num_2<<endl;} virtual void fun2(){cout<<"Base2::fun2 "<<num_2<<endl;} virtual void fun3(){cout<<"Base2::fun3 "<<num_2<<endl;}private: int num_2;};class Base3{public: Base3(int num):num_3(num){} virtual void fun1(){cout<<"Base3::fun1 "<<num_3<<endl;} virtual void fun2(){cout<<"Base3::fun2 "<<num_3<<endl;} virtual void fun3(){cout<<"Base3::fun3 "<<num_3<<endl;}private: int num_3;};class Derived1:public Base1{public: Derived1(int num):Base1(num){} virtual void fDer1_1(){cout<<"Derived1::fDer1_1\n";}//無覆蓋 virtual void fDer1_2(){cout<<"Derived1::fDer1_2\n";}};class Derived2:public Base1{public: Derived2(int num):Base1(num){} virtual void fun2(){cout<<"Derived2::fun2 "<<endl;}//只覆蓋了Base1::fun2 virtual void fDer2_1(){cout<<"Derived2::fDer2_1\n";} virtual void fDer2_2(){cout<<"Derived2::fDer2_2\n";}};class Derived3:public Base1,public Base2,public Base3//多重繼承,無覆蓋{public: Derived3(int num_1,int num_2,int num_3):Base1(num_1),Base2(num_2),Base3(num_3){} virtual void fDer3_1(){cout<<"Derived3::fDer3_1\n";} virtual void fDer3_2(){cout<<"Derived3::fDer3_2\n";}};class Derived4:public Base1,public Base2,public Base3//多重繼承,有覆蓋{public: Derived4(int num_1,int num_2,int num_3):Base1(num_1),Base2(num_2),Base3(num_3){} virtual void fun1(){cout<<"Derived4::fun1\n";}//覆蓋了所有基類的fun1函數 virtual void fDer4_1(){cout<<"Derived4::fDer4_1\n";}};int main(){ Base1 *pBase1 = NULL; Base2 *pBase2 = NULL; Base3 *pBase3 = NULL; cout<<"----- Generally inherited from Base1, no cover------\n"; Derived1 d1(1); pBase1 = &d1; pBase1->fun1(); cout<<"----- Generally inherited from Base1, covering fun2---\n"; Derived2 d2(2); pBase1 = &d2; pBase1->fun2(); cout<<"----- Multiple inheritance, no cover-----------------\n"; Derived3 d3(1,2,3); pBase1 = &d3; pBase2 = &d3; pBase3 = &d3; pBase1->fun1(); pBase2->fun1(); pBase3->fun1(); cout<<"----- Multiple inheritance, covering fun1-------------\n"; Derived4 d4(1,2,3); pBase1 = &d4; pBase2 = &d4; pBase3 = &d4; pBase1->fun1(); pBase2->fun1(); pBase3->fun1(); return 0;}/* * 程式運行結果如下:----- Generally inherited from Base1, no cover------Base1::fun1 1----- Generally inherited from Base1, covering fun2---Derived2::fun2----- Multiple inheritance, no cover-----------------Base1::fun1 1Base2::fun1 2Base3::fun1 3----- Multiple inheritance, covering fun1-------------Derived4::fun1Derived4::fun1Derived4::fun1Press <RETURN> to close this window...*/
一般繼承(無虛函數覆蓋)
Derived1類繼承自Base1類,沒有任何覆蓋基類的函數,因此Dervied1的兩個虛擬函數被依次添加到了虛函數表的尾部。Derived1的虛函數表如:
一般繼承(有虛函數的覆蓋)
Derived2繼承自Base1類,並對基類中的fun2()進行了覆蓋。所以虛函數表中的Derived2::fun2代替了Base::fun2,用時衍生類別中新的虛函數添加到虛函數的表尾。Derived2的虛函數表如:
多重繼承(無虛函數覆蓋)
Derived3繼承自Base1,Base2,Base3,其虛函數表如下:
Derived3的每個父類都有自己的虛表,所以Derived3也就有了3個虛表。這裡父類虛表的順序與聲明繼承父類的順序一致。這樣做就是為瞭解決不同的父類類型的指標指向同一個子類執行個體,而能夠調用到實際的函數。例如:
Base2 *pBase2 = new Derived3();pBase->fun2();
把Base2類型的指標指向Derived3執行個體,那麼調用將是對應Base2虛表裡的那些函數.
多重繼承(有虛函數覆蓋)
Derived4類繼承自Base1,Base2,Base3並對3個基類的fun1函數進行了覆蓋。其虛函數表如下:
可以看見基類中的fun1都被替換成了Derived4::fun1,這樣,我們就可以把任意一個靜態類型的父類指向子類,並調用子類的f()了。
Base1 *pBase1 = new Derived4();pBase1->fun1();
C++虛函數表