面试题答案
一键面试C++虚函数表在内存中的结构
- 单继承无虚函数覆盖情况:
- 每个包含虚函数的类都有一个虚函数表(vtable)。虚函数表是一个存储虚函数地址的数组。
- 当类中有虚函数时,该类的每个对象都有一个隐藏的指针(vptr),这个指针指向该类的虚函数表。
- 对于基类,虚函数表中按照声明顺序依次存储虚函数的地址。
- 单继承有虚函数覆盖情况:
- 派生类继承基类的虚函数表,并对覆盖的虚函数在虚函数表中替换为自己的函数地址。未覆盖的虚函数地址仍保持基类中的顺序。
- 多重继承情况:
- 派生类可能会有多个虚函数表,每个虚函数表对应一个基类。这是因为每个基类的虚函数布局需要保持独立。
- 派生类对象的内存布局中,会有多个vptr指针,分别指向不同基类对应的虚函数表。
- 菱形继承情况:
- 为了解决菱形继承中的数据冗余和二义性问题,引入虚继承。虚继承下,虚函数表结构会更复杂,通常会有一个虚基类表指针(vbptr)和虚基类表,虚函数表的布局也会根据虚继承的规则进行调整。
通过对象指针调用虚函数的过程
- 当通过对象指针调用虚函数时,首先根据对象的地址找到隐藏的vptr指针。
- 通过vptr指针找到对应的虚函数表。
- 在虚函数表中,根据虚函数在表中的索引找到对应的函数地址。索引是在编译期确定的,与虚函数声明顺序相关。
- 获得函数地址后,程序跳转到该地址执行虚函数的代码。
简单代码示例
#include <iostream>
class Base {
public:
virtual void func() {
std::cout << "Base::func()" << std::endl;
}
};
class Derived : public Base {
public:
void func() override {
std::cout << "Derived::func()" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->func();// 通过对象指针调用虚函数
delete basePtr;
return 0;
}
在上述代码中:
Base
类包含一个虚函数func
,Base
类对象有一个vptr指针指向其虚函数表,虚函数表中存储Base::func
的地址。Derived
类继承自Base
类并覆盖了func
函数,Derived
类对象的vptr指针指向Derived
类的虚函数表,该虚函数表中func
函数的地址被替换为Derived::func
的地址。- 在
main
函数中,basePtr
虽然是Base*
类型,但实际指向Derived
对象。当调用basePtr->func()
时,通过basePtr
找到Derived
对象的vptr,再通过vptr找到Derived
类的虚函数表,从而执行Derived::func
函数。