1. 从内存布局看继承的本质
第一次接触C++继承时,我误以为它只是语法糖。直到用gdb查看对象内存布局,才真正理解继承的底层实现。假设我们有基类Animal和派生类Dog:
cpp复制class Animal {
public:
virtual void eat() { cout << "Animal eating" << endl; }
int age;
};
class Dog : public Animal {
public:
void eat() override { cout << "Dog eating" << endl; }
void bark() { cout << "Woof!" << endl; }
string name;
};
用g++ -fdump-class-hierarchy查看内存布局会发现:
- 派生类对象包含完整的基类子对象
- 虚函数表指针(vptr)位于对象起始位置
- 成员变量按继承顺序排列
关键发现:派生类对象的内存布局 = 基类子对象 + 派生类新增成员。这解释了为什么基类指针可以指向派生类对象——它们共享相同的内存起始结构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 多态的实现机制剖析
多态的核心在于虚函数表(vtable)。每个包含虚函数的类都有自己的vtable,其中存放着虚函数指针。当调用animal->eat()时:
- 通过对象首部的vptr找到vtable
- 在vtable中查找eat()对应的槽位
- 调用槽位指向的实际函数
cpp复制// 伪代码展示动态绑定过程
void callEat(Animal* animal) {
void (**vtable)() = *(void***)animal; // 获取vtable
void (*func)() = vtable[0]; // 假设eat在第一个槽位
func(); // 动态调用
}
实测对比:
- 普通函数调用:编译期确定地址,直接call
- 虚函数调用:需要三次内存访问(vptr→vtable→func)
3. 继承中的内存陷阱
3.1 对象切片问题
将派生类对象赋值给基类对象时会发生切片:
cpp复制Dog do
