虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。在這個表中,主要是一個類的虛函數的地址,這張表解決了繼承、覆蓋的問題,其內容真實反映了實際函數。這樣,在有虛函數的類的實例中虛函數表被分配在實例的內存中,所以,當用父類的指針從操作一個子類的時候,這張虛函數表就顯得尤為重要了,它就像一張地圖,指明了實際所應該調用的函數。
在C++的標准規格說明書中寫道,編譯器必須保證虛函數表的指針存在於對象實例中最前面的位置(這是為了保證正確取到虛函數的偏移量)。這意味著通過對象實例的地址可以得到虛函數表,然後可以遍歷其中的函數指針,並調用相應的函數了。請看下面的代碼
class Base{
public:
virtual void fun1(){cout<<"Base::fun1"<<endl;}
virtual void fun2(){cout<<"Base::fun2"<<endl;}
virtual void fun3(){cout<<"Base::fun3"<<endl;}
private:
int num1;
int num2;
};
typedef void(*Fun)(void);
int main(int argc, char* argv[])
{
Base b;
Fun pFun;
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
可以看到通過函數指針pFun的調用,分別執行了對象b的3個虛函數。通過這個示例發現,可以強行把&b轉成int*,取得虛函數表的地址,然後再次取址就可以得到第一個虛函數的地址了,也就是Base::fun1()。如果調用Base::fun2()和Base::fun3(),只需要把&b先加上數組元素的偏移,後面的步驟類似。
程序中Base對象b的內存結構圖,如下圖所示:
摘自:日新為道的專欄