继承是子类对象内存中直接包含父类字段的扁平化布局,java按继承链顺序排列字段,c++通过基类前置和虚表指针实现,static成员独立存储于类区而非实例内存。

继承不是“复制父类再加点新东西”,而是子类对象在内存中直接包含父类的数据结构。父类字段不单独存在,也不额外分配空间;它们作为子类对象内存块的前缀部分,与子类字段共同构成一块连续区域。
Java中的扁平化字段布局
Java子类对象在堆中只占一个内存块,布局严格按继承链顺序排列:
- 对象头(Object Header)在最前面,用于存储锁信息、GC标记、类型指针等
- 紧接着是根父类(如 Object)的非静态字段
- 然后是直接父类的非静态字段
- 最后是子类自身声明的非静态字段
例如:class A { int id; } 和 class B extends A { String name; },创建 new B() 时,堆中只有一块内存,结构为:[Header][id][name]。字段访问靠固定偏移量——a.id 取偏移 8 字节,b.name 取偏移 12 字节(忽略对齐填充),无需查表或跳转。
C++单继承的内存线性排布
C++同样采用“基类子对象前置”的方式,但细节更依赖实现和修饰符:
- 无虚函数的普通继承:子类内存 = 基类数据成员 + 派生类数据成员,顺序与声明一致
- 含虚函数的继承:子类开头插入一个虚表指针(vptr),指向虚函数表(vtable);基类部分仍整体前置,其虚函数地址被重写或扩展到该 vtable 中
- 若子类重写了父类虚函数,对应 vtable 条目会被替换;新增虚函数则追加到 vtable 末尾
关键点在于:C++ 中的“继承”本质是内存拼接+虚机制增强,而非逻辑组合。这也解释了为什么 static_cast<base>(&derived) 能安全获得基类子对象地址——它就是同一块内存的起始偏移。
super 和 this 的本质是视角切换
Java 中 this.num 与 super.num 并非访问两个不同对象,而是对同一内存块中不同偏移位置的读取:
- 当子类定义了与父类同名字段(如都叫
num),二者共存于同一个对象中,父类num在前,子类num在后 -
this.num解析为子类字段偏移,super.num解析为父类字段偏移 - 这种设计允许编译期确定地址,不依赖运行时查找,保障性能
注意:这种同名字段覆盖虽合法,但违背封装原则,实际开发中应避免;推荐用 private 字段 + public getter/setter 统一管理状态。
静态成员与实例内存完全分离
无论 Java 还是 C++,static 成员都不参与对象内存布局:
- Java 中 static 变量存于方法区(元空间),整个类共享一份,与任何对象实例无关
- C++ 中 static 成员属于类作用域,存储在数据段(.data 或 .bss),不随每个对象重复分配
- 构造函数调用(如
super())只是初始化已存在的字段,不是“新建父类对象”
这也是为什么不能通过 this.staticVar 访问静态变量——它根本不在 this 所指的对象内存里。










