面试题答案
一键面试- 含有虚函数的类的对象内存布局:
- 当一个类含有虚函数时,对象的内存布局通常首先包含一个虚函数表指针(vptr)。这个指针指向该类的虚函数表(vtable)。
- 之后紧跟类的其他成员变量。如果类有父类,且父类也有虚函数,父类部分的内存布局同样遵循此规则,先有父类的虚函数表指针(如果父类有虚函数),然后是父类的成员变量,接着才是子类新增的成员变量。
- 虚函数表在内存中的位置:
- 虚函数表存储在程序的只读数据段(一般也称为常量区)。它是一个全局的数据结构,属于类,而不是类的对象。每个包含虚函数的类都有自己的虚函数表。
- 虚函数表与对象内存布局的关系:
- 对象中的虚函数表指针(vptr)指向对应的虚函数表。当通过对象指针或引用调用虚函数时,实际是通过虚函数表指针找到虚函数表,再从虚函数表中获取对应虚函数的地址并调用。
- 示例代码:
#include <iostream>
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;
}
};
// 辅助函数,用于获取虚函数表指针
typedef void (*Func)();
void** getVTablePtr(void* obj) {
return *(void***)(obj);
}
// 辅助函数,用于获取虚函数表中特定虚函数的地址
Func getVirtualFunctionAddr(void** vTable, int index) {
return (Func)(vTable[index]);
}
int main() {
Base* basePtr = new Derived();
// 获取对象的虚函数表指针
void** vTablePtr = getVTablePtr(basePtr);
// 获取虚函数表中第一个虚函数的地址(这里只有一个虚函数)
Func funcAddr = getVirtualFunctionAddr(vTablePtr, 0);
// 通过函数指针调用虚函数
funcAddr();
delete basePtr;
return 0;
}
在上述代码中:
Base
类是一个含有虚函数的基类,Derived
类继承自Base
类并重写了虚函数。getVTablePtr
函数用于获取对象的虚函数表指针。getVirtualFunctionAddr
函数用于从虚函数表中获取特定虚函数的地址。- 在
main
函数中,创建了一个Derived
对象并通过指针访问其虚函数表及虚函数。注意,这种通过指针直接操作虚函数表的方式在实际开发中很少使用,主要用于理解原理。实际开发中应使用正常的虚函数调用机制。