本文通過簡單例子說明子類之間發生強制轉換時虛函數如何調用,旨在對C++繼承中的虛函數表的作用機制有更深入的理解。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f()
{
cout<<"Base::f()"<<endl;
}
};
class child1:public Base
{
public:
virtual void f()
{
cout<<"child1::f()"<<endl;
}
virtual void a()
{
cout<<"child1::a()"<<endl;
}
};
class child2:public Base
{
public:
virtual void f()
{
cout<<"child2::f()"<<endl;
}
virtual void b()
{
cout<<"child2::b()"<<endl;
}
virtual void a()
{
cout<<"child2::a()"<<endl;
}
};
int main()
{
child1 c1;
child2* pc21=(child2*)&c1;
pc21->b();//輸出 child1::a()
// pc21->a();//訪問越界,程序運行時崩潰
child2 c2;
child1* pc12=(child1*)&c2;
pc12->a();//輸出 child2::b()
return 0;
}
結論:
1、通常的類型強轉是告訴編譯器必須按照指定結構的內存布局來解析對應內存,如上例中“child2* pc21=(child2*)&c1; ”,編譯器會把c1對應的內存來當做Derive的內存布局來解析。因為在對象c1對應的類child1的虛函數表中,共存在三個函數,分別為f() b() a(),其中函數b()是第二個,因此編譯器就會把對象c1對應的內存來當做類child2的內存布局來解析(注意內存裡的內容不變,還是c1的,即為類child1的內存布局,在這裡只有虛函數表),此時在類child1的虛函數表中也找第二個函數,找到了函數a(),因此輸出“child1::a()”,運行正常。但這種行為可能是危險的,若使用的內存布局並不適合真實內存,很可能造成訪問越界等問題(如上例中的“pc21->a();”,這次就在類B的虛函數表中找第三個函數,結果沒有找到(訪問越界),函數運行時崩潰。),因此使用強制轉換操作時應特別注意。
2、通過上述例子可知,虛函數在虛函數表中的存儲順序是與聲明順序一致的,而不是虛函數名字的字符串排序,如本例中為f() b() a(),雖然編程時的自動補全提示框中顯示的順序是a() b() f(),但可能已經經過內部優化,這個就不太清楚了(也不是我們要研究的內容)。