多態是C++的三大特性之一,是通過虛函數表來實現的。關于虛函數表:
- 每個含有虛函數的類都有一張虛函數表(vtbl),表中每一項是一個虛函數的地址, 也就是說,虛函數表的每一項是一個虛函數的指針。
- 如果繼承的父類中有虛函數,那么子類中也會有虛函數表。
- 沒有虛函數的C++類,是不會有虛函數表的。
- 一個含有虛函數表的類實例化對象后,對象并不會直接存儲虛函數表,而是在開始的位置存放了一個指向虛函數表的指針。
例1,沒有虛函數的情況
#include <iostream>
using namespace std;
class base
{
void f(){cout<<"base::f"<<endl;};
void g(){cout<<"base::g"<<endl;};
void h(){cout<<"base::h"<<endl;};
};
int main(int argc, const char * argv[]) {
cout<<"size of Base: "<<sizeof(Base)<<endl;
return 0;
}
運行結果:size of Base: 1
。這里簡單說明一下,C++的成員方法不占用內存,size為1是因為實例化對象需要在內存中分配一塊地址,所以編譯器就分配了一個字節給空類。
例2,有虛函數的情況
class Base {
public:
virtual void f() {cout<<"base::f"<<endl;}
virtual void g() {cout<<"base::g"<<endl;}
virtual void h() {cout<<"base::h"<<endl;}
};
int main(int argc, const char * argv[]) {
cout<<"size of Base: "<<sizeof(Base)<<endl;
return 0;
}
運行結果:size of Base: 8
。這8個字節,就是虛函數表的指針。
例3,下面,我們嘗試通過地址偏移的方式來調用虛函數:
class Base {
public:
virtual void f() {cout<<"base::f"<<endl;}
virtual void g() {cout<<"base::g"<<endl;}
virtual void h() {cout<<"base::h"<<endl;}
};
int main(int argc, const char * argv[]) {
typedef void(*Func)(void);
Base b;
Base *d = &b;
long* pvptr = (long*)d; //獲取指向虛函數表的指針,即b對象的首地址
cout << "vtable address:" << *pvptr << endl; //打印虛函數表的地址
long* vptr = (long*)*pvptr; //虛函數表的首地址
Func f = (Func)(*vptr); //虛函數表首地址的值為第一個虛函數的函數指針
Func g = (Func)(*(vptr+1)); //繼續偏移指針,找到第二個虛函數指針
Func h = (Func)(*(vptr+2)); //以此類推
// 下面這種寫法與上面實際效果相同,只是指針偏移方式不同而已
// Func f = (Func)vptr[0];
// Func g = (Func)vptr[1];
// Func h = (Func)vptr[2];
f();
g();
h();
return 0;
}
運行結果:
vtable address:4294975712
base::f
base::g
base::h
參考文章:
下面兩篇文章都講的比較細,從無繼承到多繼承,圖文并茂,有興趣的同學可以學習一下!
http://www.cnblogs.com/Ripper-Y/archive/2012/05/15/2501930.html
http://www.cppblog.com/dawnbreak/archive/2009/03/10/76084.html