大家知道虛函數是通過一張虛函數表來實現的。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,其內容真是反應實際的函數。這樣,在有虛函數的類的實例中,這個表分配在了這個實例的內存中,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤為重要了。它就像一個地圖一樣,指明了實際所應該調用的函數。
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::fun1 Base::fun2 Base::fun3 Press <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 1 Base2::fun1 2 Base3::fun1 3 ----- Multiple inheritance, covering fun1------------- Derived4::fun1 Derived4::fun1 Derived4::fun1 Press <RETURN> to close this window... */
一般繼承(無虛函數覆蓋)
Derived1類繼承自Base1類,沒有任何覆蓋基類的函數,因此Dervied1的兩個虛擬函數被依次添加到了虛函數表的尾部。Derived1的虛函數表如下圖:
一般繼承(有虛函數的覆蓋)
Derived2繼承自Base1類,並對基類中的fun2()進行了覆蓋。所以虛函數表中的Derived2::fun2代替了Base::fun2,用時派生類中新的虛函數添加到虛函數的表尾。Derived2的虛函數表如下圖:
多重繼承(無虛函數覆蓋)
Derived3繼承自Base1,Base2,Base3,其虛函數表如下:
Base2 *pBase2 = new Derived3(); pBase->fun2();
把Base2類型的指針指向Derived3實例,那麼調用將是對應Base2虛表裡的那些函數.
多重繼承(有虛函數覆蓋)
Derived4類繼承自Base1,Base2,Base3並對3個基類的fun1函數進行了覆蓋。其虛函數表如下:
Base1 *pBase1 = new Derived4(); pBase1->fun1();