面试题答案
一键面试派生类对象内存布局
- 基类部分:派生类对象首先包含基类的所有成员(包括数据成员和成员函数指针等),按照基类定义的顺序存储在内存中。如果基类有虚函数,那么基类部分会有一个虚函数指针
vptr
,它指向基类的虚函数表vtable
。 - 派生类新增部分:在基类部分之后,接着存储派生类新增的数据成员,按照派生类定义的顺序排列。如果派生类重写了基类的虚函数,虚函数表中的对应条目会被替换为派生类中重写的虚函数的地址;若派生类有新增的虚函数,则虚函数表会在原有基础上扩充,增加对应新虚函数的条目。
虚函数表(vtable)和虚函数指针(vptr)在多态实现过程中的工作原理
- 虚函数表(vtable):
- 每个包含虚函数的类都有一个虚函数表。虚函数表是一个数组,数组中的每个元素是一个函数指针,指向类中的虚函数。对于基类和派生类,它们各自有自己的虚函数表。如果派生类重写了基类的虚函数,派生类虚函数表中对应基类虚函数的条目会指向派生类重写后的虚函数地址。如果派生类有新的虚函数,虚函数表会在末尾添加新的条目指向这些新虚函数。
- 虚函数指针(vptr):
- 每个包含虚函数的对象都有一个虚函数指针
vptr
。在对象构造过程中,编译器会自动将vptr
初始化为指向该对象所属类的虚函数表。对于派生类对象,vptr
指向派生类的虚函数表。
- 每个包含虚函数的对象都有一个虚函数指针
- 多态实现:
- 当通过基类指针或引用调用虚函数时,运行时系统首先根据对象的
vptr
找到对应的虚函数表。然后,根据虚函数在虚函数表中的索引(即偏移量),找到要调用的实际虚函数的地址,并执行该函数。这使得程序能够在运行时根据对象的实际类型来决定调用哪个版本的虚函数,从而实现多态。例如:
- 当通过基类指针或引用调用虚函数时,运行时系统首先根据对象的
class Base {
public:
virtual void virtualFunction() {
std::cout << "Base::virtualFunction" << std::endl;
}
};
class Derived : public Base {
public:
void virtualFunction() override {
std::cout << "Derived::virtualFunction" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->virtualFunction(); // 通过 vptr 和 vtable 调用 Derived::virtualFunction
delete basePtr;
return 0;
}
在上述代码中,basePtr
是基类指针,但指向的是派生类对象。当调用 virtualFunction
时,程序通过 basePtr
所指对象的 vptr
找到派生类的虚函数表,再根据虚函数表找到 Derived::virtualFunction
并执行。