虚函数表
即多态的原理
- 覆盖和隐藏只是表象,实际是通过虚函数表来实现的(联想到虚基类的继承中的虚基表)
//查看虚函数的内容
class Base
{
public:
Base()
{
base_data = 10;
}
public:
virtual void fun()
{
cout<<"This is Base::fun()"<<endl;
}
virtual void show()
{
cout<<"This is Base;;show()"<<endl;
}
void fun1()
{
cout<<"This is Base::fun1()"<<endl;
}
private:
int base_data;
};
class D : public Base
{
public:
D()
{
d_data = 8;
}
public:
void fun()
{
cout<<"This is D::fun()"<<endl;
}
void show()
{
cout<<"This is D::show()"<<endl;
}
virtual void print()
{
cout<<"This is D::print()"<<endl;
}
private:
int d_data;
};
typedef void(*vfptr_type)(); //将函数指针类型重命名为vfptr_type
void PrintVTable(vfptr_type vtable[])
{
cout<<"虚表地址:>"<<vtable<<endl;
//由于虚函数表存放的函数指针个数不知道,但是虚函数表的最后一个函数指针的结尾后面是nullptr,由此便可访问到虚函数表中所有的函数指针
for(int i=0; vtable[i]!=nullptr; ++i)
{
vtable[i](); //(vtable+1)(); //虚函数表中的函数指针解引用
}
}
void main()
{
//Base b;
D d;
cout << sizeof(d) << endl;
vfptr_type *vtable = (vfptr_type*)(*(int*)&d);
//由于 创建的派生类对象从起始位置开始存放的分别时vfptr(32bit系统下是4bit,64bit系统下是8bit),基类数据成员,派生类数据成员
//所以将派生类对象的地址强转成int*类型就相当于拿到了vfptr,由于虚函数表是不占类的空间只是同一个虚函数表指针指向,所以对其解引用就拿到了虚函数表的首元素地址
//通过之前的强制类型转换指针无法正确偏移函数指针类型大小,所以再强转回去
PrintVTable(vtable);
}
重写的原理是,基类和派生类的虚函数都会存放在虚函数表中,只不过构成重载的派生类函数地址会覆盖掉基类的函数地址,所以构成了覆盖的假象
隐藏的原理是
???
VS下虚函数表是存在代码段的
- 一般继承(无虚函数覆盖)
基类和派生类的虚函数按照其声明顺序放于虚函数表中 父类的虚函数在子类的虚函数前面
一般继承(有虚函数覆盖)
虚函数表中派生类中与基类中三同的虚函数,派生类的虚函数指针会覆盖掉基类的虚函数指针
多重继承(无虚函数覆盖)
每个基类都有一个虚函数表指针指向一个虚函数表,每个基类指向的虚函数表存放自己基类的虚函数
派生类的虚函数按声明顺序存放在第一个(按继承顺序的第一个)基类所指向的虚函数表的后面,并且父类的虚函数在子类的虚函数前面
多重继承(有虚函数覆盖)
每个基类都有一个虚函数表指针指向一个虚函数表,每个基类指向的虚函数表存放自己基类的虚函数
派生类的虚函数按声明顺序存放在第一个(按继承顺序的第一个)基类所指向的虚函数表的后面,与基类虚函数三同的会覆盖基类的虚函数地址,并且父类的虚函数在子类的虚函数前面