核心要点速览

  • 继承:解决代码复用与扩展,核心是派生类获取基类成员;继承方式分 public/protected/private,构造先父后子、析构先子后父
  • 多态:解决接口统一与动态行为,核心是基类指针 / 引用指向派生类对象,调用虚函数触发动态绑定
  • 机制:虚函数(virtual)、纯虚函数(抽象类)、虚函数表(vtable)+ 虚指针(vptr)、虚析构(避免资源泄漏)
  • 对比:重写(Override)vs 重载(Overload)vs 隐藏(Hide)

一、继承:代码复用与层次化设计

继承是派生类从基类获取成员(属性 + 行为)的机制,核心是 “基于已有类扩展功能”。

1. 继承方式与成员访问权限

继承方式决定基类成员在派生类中的访问权限,常用public继承(其他方式极少用)。

基类成员权限 public 继承(常用) protected 继承 private 继承
public 派生类 public 派生类 protected 派生类 private
protected 派生类 protected 派生类 protected 派生类 private
private 派生类不可访问 派生类不可访问 派生类不可访问

2. 派生类的构造与析构

构造函数

  • 执行顺序先调用基类构造函数(初始化基类部分)→ 再调用派生类构造函数(初始化新增部分)。
  • 重点:若基类无默认构造函数(无参 / 全默认参数),派生类必须在初始化列表中显式调用基类带参构造(否则编译报错)。

析构函数

  • 执行顺序先调用派生类析构函数(清理派生类资源)→ 再调用基类析构函数(清理基类资源)。
  • 重点:基类析构函数必须加virtual(虚析构),否则通过基类指针删除派生类对象时,仅调用基类析构,导致派生类资源泄漏。

3. 菱形继承与虚继承

菱形继承问题

  • 场景:派生类间接继承同一基类多次(如 D 继承 B 和 C,B 和 C 均继承 A)。
  • 后果:基类成员在派生类中存在多份拷贝,导致数据冗余访问二义性(如d.a无法确定访问哪个 A 的成员)。

解决方案:虚继承

  • 语法:在间接继承路径中加virtual关键字(如class B : virtual public A)。
  • 原理:虚继承的派生类通过虚基类表指针(vbptr) 间接访问基类成员,确保基类在最终派生类中仅存一份实例。
  • 规则:虚基类的构造函数由最终派生类负责初始化(而非直接派生类)。

二、多态:接口复用与动态行为

多态是 “同一接口,不同实现”,按绑定时机分静态多态和动态多态,核心考察动态多态。

1. 静态多态(编译期多态)

  • 绑定时机:编译阶段确定调用哪个函数。
  • 实现方式
    • 函数重载:同一作用域内同名函数,参数列表(类型 / 个数 / 顺序)不同。
    • 模板:参数类型不同时,编译器生成不同版本的函数 / 类。

2. 动态多态(运行期多态)

  • 绑定时机:运行阶段根据对象实际类型确定调用的函数。
  • 实现条件(缺一不可):
    1. 基类声明虚函数(加virtual关键字)。
    2. 派生类重写(override显式标注)该虚函数。
    3. 通过基类指针或引用指向派生类对象,调用虚函数。

(1)虚函数重写规则

  • 派生类函数与基类函数的函数名、参数列表、返回类型完全一致(返回类型可协变:基类返回Base*,派生类可返回Derived*)。
  • 基类函数必须带virtual(派生类可省略virtual,但不推荐,显式标注更清晰)。
  • 访问权限可不同(如基类public,派生类protected),但通过基类指针调用时需满足基类访问权限。

(2)底层实现:虚函数表(vtable)+ 虚指针(vptr)

  • 虚函数表(vtable):每个含虚函数的类(基类 / 派生类)有一个全局唯一的 vtable,存储该类所有虚函数的地址。
  • 虚指针(vptr):每个对象包含一个 vptr 成员(64 位系统占 8 字节),指向所属类的 vtable。
  • 动态绑定流程
    1. 基类声明虚函数,编译器为基类生成 vtable,存入基类虚函数地址。
    2. 派生类重写虚函数,编译器为派生类生成 vtable,重写函数地址替换为派生类版本,未重写的仍指向基类版本。
    3. 对象创建时,vptr 初始化指向所属类的 vtable。
    4. 调用虚函数(如base_ptr->func()),通过 vptr 找到 vtable,调用对应函数地址(运行时确定)。

(3)纯虚函数与抽象类

  • 纯虚函数:未实现的虚函数,语法virtual void func() = 0;,用于定义接口。
  • 抽象类:含纯虚函数的类,不能实例化对象(仅作为基类)。
  • 派生类规则:必须实现基类所有纯虚函数,否则仍是抽象类(无法实例化)。

三、概念区分

重写(Override)vs 重载(Overload)vs 隐藏(Hide)

对比维度 重写(Override) 重载(Overload) 隐藏(Hide)
作用域 不同作用域(基类 vs 派生类) 同一作用域(如同一类 / 命名空间) 不同作用域(基类 vs 派生类)
函数关系 基类虚函数与派生类同名函数 同名函数 派生类函数与基类同名,但不满足重写
关键条件 函数名、参数、返回值完全一致 参数列表不同(类型 / 个数 / 顺序) 不满足重写条件(如基类非虚函数)
多态类型 动态多态(运行期绑定) 静态多态(编译期绑定) 无多态(直接调用派生类函数)
访问基类函数 直接调用(基类指针 / 引用触发) 按实参类型匹配 需用基类名限定(Base::func()

虚函数的限制

  1. 静态成员函数不能是虚函数(无this指针,无法访问 vptr,无法动态绑定)。
  2. 构造函数不能是虚函数(对象构造时 vptr 尚未初始化,无法访问 vtable)。
  3. 内联函数可以是虚函数,但触发动态绑定时,内联优化失效(内联是编译期,动态绑定是运行期)。
  4. 友元函数不能是虚函数(友元不是类成员,无this指针,无法关联 vtable)。