例7-1和例8-1很好理解,我把這兩個例子放在這裏,是讓大家作一個比較擺了,也是爲了幫助大家更好的理解:
例7-1中,派生類沒有覆蓋基類的虛函數,此時派生類的vtable中的函數指針指向的地址就是基類的虛函數地址。
例8-1中,派生類覆蓋了基類的虛函數,此時派生類的vtable中的函數指針指向的地址就是派生類自己的重寫的虛函數地址。
在例7-2和8-2看起來有點怪怪,其實,你按照上面的原則對比一下,答案也是明朗的:
例7-2中,我們爲派生類重載了一個函數版本:void fun(double d) 其實,這只是一個障眼法。我們具體來分析一下,基類共有幾個函數,派生類共有幾個函數:
類型
|
基類
|
派生類
|
Vtable部分
|
void fun(int i)
|
指向基類版的虛函數void fun(int i)
|
靜態部分
|
|
void fun(double d)
|
我們再來分析一下以下三句代碼
Base *pb = new Derive();
pb->fun(1);//Base::fun(int i)
pb->fun((double)0.01);//Base::fun(int i)
這第一句是關鍵,基類指針指向派生類的對象,我們知道這是多態調用;接下來第二句,運行時基類指針根據運行時對象的類型,發現是派生類對象,所以首先到派生類的vtable中去查找派生類的虛函數版本,發現派生類沒有覆蓋基類的虛函數,派生類的vtable只是作了一個指向基類虛函數地址的一個指向,所以理所當然地去調用基類版本的虛函數。最後一句,程序運行仍然埋頭去找派生類的vtable,發現根本沒有這個版本的虛函數,只好回頭調用自己的僅有一個虛函數。
這裏還值得一提的是:如果此時基類有多個虛函數,此時程序編繹時會提示”調用不明確”。示例如下
#include <iostream>
using namespace std;
class Base{
public:
virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }
virtual void fun(char c){ cout <<"Base::fun(char c)"<< endl; }
};
class Derive : public Base{
public:
void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }
};
int main()
{
Base *pb = new Derive();
pb->fun(0.01);//error C2668: 'fun' : ambiguous call to overloaded function
delete pb;
return 0;
}
好了,我們再來分析一下例8-2。
n 例8-2中,我們也爲派生類重載了一個函數版本:void fun(double d) ,同時覆蓋了基類的虛函數,我們再來具體來分析一下,基類共有幾個函數,派生類共有幾個函數:
類型
|
基類
|
派生類
|
Vtable部分
|
void fun(int i)
|
void fun(int i)
|
靜態部分
|
|
void fun(double d)
|
從表中我們可以看到,派生類的vtable中函數指針指向的是自己的重寫的虛函數地址。
我們再來分析一下以下三句代碼
Base *pb = new Derive();
pb->fun(1);//Derive::fun(int i)
pb->fun((double)0.01);//Derive::fun(int i)
第一句不必多說了,第二句,理所當然調用派生類的虛函數版本,第三句,嘿,感覺又怪怪的,其實呀,C++程序很笨的了,在運行時,埋頭闖進派生類的vtable表中(虛函數表virtual function table),隻眼一看,靠,競然沒有想要的版本,真是想不通,基類指針爲什麼不四處轉轉再找找呢?呵呵,原來是眼力有限,基類年紀這麼老了,想必肯定是老花了,它那雙眼睛看得到的僅是自己的非Vtable部分(即靜態部分)和自己要管理的Vtable部分,派生類的void fun(double d)那麼遠,看不到呀!再說了,派生類什麼都要管,難道派生類沒有自己的一點權力嗎?哎,不吵了,各自管自己的吧^_^
唉!你是不是要嘆氣了,基類指針能進行多態調用,但是始終不能進行派生類的重載調用啊(參考例6)~~~
再來看看例9,本例的效果同例6,異曲同工