大家知道虛函數是通過一張虛函數表來實現的。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,其內容真是反應實際的函數。這樣,在有虛函數的類的實例中,這個表分配在了這個實例的內存中,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤為重要了。它就像一個地圖一樣,指明了實際所應該調用的函數。
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();