简介:
如果一个类中声明有虚函数,大多数编译器就会为这个类创建一个虚函数表(virtual function table)简称虚表(vtab)。
虚表中存放了该类中所有虚函数的地址,虚表属于整个类,不属于某个具体的对象,该类所有对象共享虚表。
当实例化该类对象时,会自动把该类虚表的首地址(简称为 虚指针 vptr)存在该对象中,当使用基类指针/基类引用 调用该虚函数时,大概的过程为:
基类指针/基类引用 -》具体对象 -》虚指针 -》 虚函数表 -》 具体虚函数地址 -》调用虚函数
大概图示如下:

验证代码:
#include <iostream>
using namespace std;
class Parent {
public:
virtual void func1() {
cout << "Parent::func1()" << endl;
}
virtual void func2() {
cout << "Parent::func2()" << endl;
}
private:
int a;
virtual void func3() {
cout << "Parent::func3()" << endl;
}
};
class Child : public Parent{
public:
/* 重写func2函数 */
void func2() {
cout << "Child::func2()" << endl;
}
private:
int b;
};
int main() {
Child c;
Parent *p;
p = &c;
/* 取基类对象的地址 */
Child *pc = &c;
/* 因为64位环境下指针大小为8,所以将其转换为long型的方便解引用 */
long *pl = reinterpret_cast<long *>(pc);
/* 将long型的地址转换为long*的指针 */
long *vptr = reinterpret_cast<long *>(*pl);
/* 定义一个函数指针PF */
using PF = void (*)();
/* 提取虚函数表中第一个元素 */
PF pf1 = reinterpret_cast<PF>(vptr[0]);
pf1();
/* 提取虚函数表中第二个元素 */
PF pf2 = reinterpret_cast<PF>(vptr[1]);
pf2();
/* 提取虚函数表中第三个元素 */
PF pf3 = reinterpret_cast<PF>(vptr[2]);
pf3();
cout << "===================" << endl;
/* 通过基类的指针调用 */
p->func1();
p->func2();
return 0;
}
运行结果:
总结:
如果一个类中声明有虚函数,大多数编译器就会为这个类创建一个虚函数表,虚表中存放了该类中所有虚函数的地址,虚表属于整个类,不属于某个具体的对象,该类所有对象共享虚表。在子类中如果没有重写虚函数,会继承父类的虚函数,重写则会覆盖父类的虚函数。
彩蛋:
再验证过程中在父类中定义了一个私有的虚函数,但是在类外依旧能通过虚表地址调用它,从另一方面可以看出类的公有和私有权限只在编译阶段会判断。