文章鏈接:http://blog.csdn.net/haoel/article/details/1948051
虛函數表
C++中的虛函數的作用主要是實現了多態的機制。關於多態,簡而言之就是用父類型別的指針指向其子類的實例,然後通過父類的指針調用實際子類的成員函數。
對C++ 瞭解的人都應該知道虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱爲V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其內容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中。C++的編譯器應該是保證虛函數表的指針存在於對象實例中最前面的位置。這意味着我們通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。
代碼:
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
virtual ~Base() {}
};
typedef void(*Fun) (void);
int main()
{
Base b;
Fun pFun = NULL;
cout << "虛函數表地址" << (int *)(&b) << endl;
cout << "虛函數表--第一個虛函數地址" << (int*)*(int*)(&b)<< endl;
pFun = (Fun)*(int *)*(int*)(&b);
pFun();
return 0;
}
輸出:
虛函數表地址:0x22ff48
虛函數表--第一個虛函數地址:0x4041d0
Base::f
對*(int*)*(int*)(&b)的理解:
如上圖所示:實際就是一個指向指針的指針,需要兩次解引用才能得到存儲在虛函數表中的函虛數指針。
B:Base類對象;
&b: Base類對象的首地址,因爲 C++的編譯器應該保證虛函數表的指針存在於對象實例中最前面的位置,也就是該地址存儲的內容應該是虛函數表的地址;
(int *)(&b):對其進行強制類型轉換成(int *)的目的是當對其執行間接訪問操作時,可以訪問到以該地址開始的連續四個字節的內存空間,因爲指針總是要佔四個字節的。
*(int *)(&b):第一次解引用,指向虛函數表,其值爲虛函數表首字節的內容(虛函數表裏存儲的都是虛函數指針);
(int *)*(int *)(&b):對其進行強制類型轉換成(int *)的目的是當對其執行間接訪問操作時,可以訪問到以該地址開始的連續四個字節的內存空間,因爲指針總是要佔四個字節的。強制類型轉換之後該值表示虛函數指針表中第一個函數指針的地址,
*(int *)*(int *)(&b):第二次解引用。得到虛函數指針表中第一個函數指針,可以調用虛函數。
一般繼承(無虛函數覆蓋)
代碼如下:
using namespace std;
typedef void(*Fun) (void);
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
virtual ~Base() {}
};
class Derived: public Base
{
public:
virtual void f1() { cout << "Derived::f1" << endl; }
virtual void g1() { cout << "Derived::g1" << endl; }
virtual void h1() { cout << "Derived::h1" << endl; }
virtual ~Derived() {}
};
int main()
{
Base b;
Derived d;
Fun pFun = NULL;
int **pVtab = (int**)(&d);
for(int i = 0; NULL != (Fun)pVtab[0][i] && i<3; i++)
{
pFun = (Fun)pVtab[0][i];
pFun();
}
return 0;
}
按文章所述,派生類對象d的虛函數表如下:
但未得到驗證。但代碼中for循環的條件爲"i<3"時,可以輸出繼承自Base類的3個虛函數。
但將for循環的條件改爲”i<6”時,執行時會報讀內存錯誤,在gcc和VS2010下都是如此。下圖是在VS2010下得到的虛函數表:
一般繼承(有虛函數覆蓋)
代碼如下:#include <iostream>
using namespace std;
typedef void(*Fun) (void);
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
virtual ~Base() {}
};
class Derived: public Base
{
public:
virtual void f1() { cout << "Derived::f1" << endl; }
virtual void g1() { cout << "Derived::g1" << endl; }
virtual void h1() { cout << "Derived::h1" << endl; }
virtual void f() { cout << "Derived::f" << endl; }
virtual void g() { cout << "Derived::g" << endl; }
virtual void h() { cout << "Derived::h" << endl; }
virtual ~Derived() {}
};
int main()
{
Base b;
Derived d;
Fun pFun = NULL;
int **pVtab = (int**)(&d);
for(int i = 0; NULL != (Fun)pVtab[0][i] && i<6; i++)
{
pFun = (Fun)pVtab[0][i];
pFun();
}
return 0;
}
按文章所述,派生類對象d的虛函數表如下:
代碼中for循環的條件爲"i<3"時,可以輸出派生類d重載的三個虛函數,說明派生類的f(), g(), h()函數將繼承自Base類的這個三個函數覆蓋。
但將for循環的條件改爲”i<6”時,執行時仍會報讀內存錯誤。下圖是在VS2010下得到的虛函數表:
多重繼承(無虛函數覆蓋)
代碼如下:
#include <iostream>
using namespace std;
typedef void(*Fun) (void);
class Base1
{
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
virtual ~Base1() {}
};
class Base2
{
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
virtual ~Base2() {}
};
class Base3
{
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual ~Base3() {}
};
class Derived: public Base1, public Base2, public Base3
{
public:
virtual void f1() { cout << "Derived::f1" << endl; }
virtual void g1() { cout << "Derived::g1" << endl; }
virtual void h1() { cout << "Derived::h1" << endl; }
virtual ~Derived() {}
};
int main()
{
Derived d;
Fun pFun = NULL;
int **pVtab = (int**)(&d);
for(int i = 0; i<3; i++)
{
for(int j = 0; NULL != (Fun)pVtab[i][j] && i<6; j++)
{
pFun = (Fun)pVtab[i][j];
pFun();
}
}
return 0;
}
按文章所述,派生類對象d的虛函數表如下: