先来查看一简单例子
#include<iostream>
using namespace std;
class Base{
public:
virtual void f() { cout << "base f()被call"<<endl; }
virtual void g() { cout << "父类虚函数G被call" << endl; }
};
class Derive : public Base{
public:
virtual void f() { cout << "子类虚函数f()被call" << endl; }
};
int main()
{
Derive d; //子类对象
Base *pb;//父类指针
pb = &d;//父类指针指向子类对象
int *q =(int*) pb;//取出类对象地址
void(*f)(Derive*) = (void(*)(Derive*))(*(int*)(*q));//取出f()函数地址
void(*g)(Derive*) = (void(*)(Derive*))(*(int*)(*q+4));//取出g()函数地址
f((Derive*)pb);//调用子对象虚函数表的第一个元素
g((Derive*)pb);//调用子对象虚函数表的第二个元素
system("pause");
return 0;
}
用VS2015调试一番.查看pb保存子对象的地址.
以上pb保存了子类对象的地址,地址为0x003bfb64.来看看.这个子类对象的首4个字节(32位机子)地址保存了啥内容(其实是虚函数表的地址)
.
子类对象首4个字节保存了00948b34这个数值.(这个其实是虚函数表的地址).再来看看这个数值保存了啥内容.
发现这个地址开始 0094142e跟00941424应该就是虚函数的地址了.不信.咋们到反汇编查看该地址内容.
先查看0x0094142e(函数转为反汇编.其实第一个地址就是跳转).发现没.反汇编已经提示了是Derive::f这个函数
再看看0x00941424.是Base::g这个函数
以上可知.一般编译器实现.把虚函数表的地址用放在子对象首4个字节.虚函数表保存的是虚函数的地址.
void(*f)(Derive*) = (void(*)(Derive*))(*(int*)(*q));//取出f()函数地址
void(*g)(Derive*) = (void(*)(Derive*))(*(int*)(*q+4));//取出g()函数地
比如以上2行.q是子对象的地址.(*q)就是虚函数表的地址 (*q)+4 相当于虚函数的第二个元素的地址. *(int*)(*q)表示第一个虚函数的地址.*(int*)(*q+4));表示第二个虚函数表的地址..最后强制转换为类成员函数.用(void(*)(Derive*).因为一般类成员函数在编译后.首形参是一个this指针.这里用Derive*.因为this指针的类型就是类的类型
f((Derive*)pb);//调用子对象虚函数表的第一个元素
g((Derive*)pb);//调用子对象虚函数表的第二个元素
最后调用这2个虚函数表中的虚函数.把pb(子对象的地址)当做参数this指针.然后直接调用.即可使得函数被调用了