參考:http://c.biancheng.net/view/267.html
1、說明
我們都知道多型指的是父類的指標在執行中指向子類,那麼它的實現原理是什麼呢?答案是虛擬函式表
在 關於virtual 一文中,我們詳細瞭解了C++多型的使用方式,我們知道沒有 virtual 關鍵子就沒法使用多型
2、虛擬函式表
我們看一下下面的程式碼
class A
{
public:
int i;
virtual void func() { cout << "A func" << endl; }
virtual void func2() { cout << "A func2" << endl; }
void func3() { cout << "A func3" << endl; }
};
class B : public A
{
int j;
void func() { cout << "B func" << endl; }
void func3() { cout << "B func3" << endl; }
};
int main()
{
cout << sizeof(A) << ", " << sizeof(B); //輸出 8,12
return 0;
}
在32位編譯模式下,程式的執行結果是:8,12
但是如果把程式碼中的 virtual 刪掉,則程式的執行結果為:4,8
可以發現,有了虛擬函式之後,類所佔的儲存空間比沒有虛擬函式多了4個位元組,這個4個位元組就是實現多型的關鍵 -- 位於物件儲存空間的最前端的指標,存放的是 虛擬函式表的地址,這個是由編譯器實現的
每個帶有虛擬函式的類(包括其子類)都有虛擬函式表
虛擬函式表中存放著虛擬函式的地址,注意是虛擬函式的地址,非虛擬函式不在此列
虛擬函式表是編譯器實現的,程式執行時被載入記憶體,一個類的虛擬函式表中列出了該類的全部虛擬函式地址。
例如,上面程式碼中,類A的物件的儲存空間以及虛擬函式表如圖所示:
類B的物件的儲存空間以及虛擬函式表,如下圖所示:
多型的函式呼叫語句被編譯成根據基類指標所指向的物件中存放的虛擬函式表的地址,在虛擬函式表中查詢虛擬函式地址,並呼叫虛擬函式的一系列指令
3、程式碼示例
在上面程式碼的基礎上
A* p = new B();
p->func(); //B func
p->func3(); //A func3
p->func2(); //A func
第二行程式碼執行如下:
- 取出 p 指標所指向的位置的前4個位元組,即物件所屬的類(類B)的虛擬函式表的地址(64位編譯模式下是8個位元組);
- 根據虛擬函式表的地址找到虛擬函式表,並在虛擬函式表中查詢要呼叫的虛擬函式地址;
- 呼叫虛擬函式;
到此,我們應該不難理解,上面第二行和第三行程式碼執行的分別是類A和類B的方法
執行 p->func(); 找的是類B虛擬函式表中 func() 地址,因為類B重寫了,所以儲存的是類B的func()地址
而執行 p->func3(); 的時候,發現 func3() 不是虛擬函式,所以並沒有找虛擬函式列表,而是直接呼叫的p(類A型別)的方法
同樣的,執行 p->func2(); 的時候,找的也是類B的虛擬函式表,因為類B沒有重寫 func2,所以存的是類A的虛擬函式 func2() 的地址,所以執行了類A的 func2() 方法