面向对象编程OOP:继承与多态
核心要点速览
- 继承:解决代码复用与扩展,核心是派生类获取基类成员;继承方式分 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. 动态多态(运行期多态)
- 绑定时机:运行阶段根据对象实际类型确定调用的函数。
- 实现条件(缺一不可):
- 基类声明虚函数(加
virtual关键字)。 - 派生类重写(
override显式标注)该虚函数。 - 通过基类指针或引用指向派生类对象,调用虚函数。
- 基类声明虚函数(加
(1)虚函数重写规则
- 派生类函数与基类函数的函数名、参数列表、返回类型完全一致(返回类型可协变:基类返回
Base*,派生类可返回Derived*)。 - 基类函数必须带
virtual(派生类可省略virtual,但不推荐,显式标注更清晰)。 - 访问权限可不同(如基类
public,派生类protected),但通过基类指针调用时需满足基类访问权限。
(2)底层实现:虚函数表(vtable)+ 虚指针(vptr)
- 虚函数表(vtable):每个含虚函数的类(基类 / 派生类)有一个全局唯一的 vtable,存储该类所有虚函数的地址。
- 虚指针(vptr):每个对象包含一个 vptr 成员(64 位系统占 8 字节),指向所属类的 vtable。
- 动态绑定流程:
- 基类声明虚函数,编译器为基类生成 vtable,存入基类虚函数地址。
- 派生类重写虚函数,编译器为派生类生成 vtable,重写函数地址替换为派生类版本,未重写的仍指向基类版本。
- 对象创建时,vptr 初始化指向所属类的 vtable。
- 调用虚函数(如
base_ptr->func()),通过 vptr 找到 vtable,调用对应函数地址(运行时确定)。
(3)纯虚函数与抽象类
- 纯虚函数:未实现的虚函数,语法
virtual void func() = 0;,用于定义接口。 - 抽象类:含纯虚函数的类,不能实例化对象(仅作为基类)。
- 派生类规则:必须实现基类所有纯虚函数,否则仍是抽象类(无法实例化)。
三、概念区分
重写(Override)vs 重载(Overload)vs 隐藏(Hide)
| 对比维度 | 重写(Override) | 重载(Overload) | 隐藏(Hide) |
|---|---|---|---|
| 作用域 | 不同作用域(基类 vs 派生类) | 同一作用域(如同一类 / 命名空间) | 不同作用域(基类 vs 派生类) |
| 函数关系 | 基类虚函数与派生类同名函数 | 同名函数 | 派生类函数与基类同名,但不满足重写 |
| 关键条件 | 函数名、参数、返回值完全一致 | 参数列表不同(类型 / 个数 / 顺序) | 不满足重写条件(如基类非虚函数) |
| 多态类型 | 动态多态(运行期绑定) | 静态多态(编译期绑定) | 无多态(直接调用派生类函数) |
| 访问基类函数 | 直接调用(基类指针 / 引用触发) | 按实参类型匹配 | 需用基类名限定(Base::func()) |
虚函数的限制
- 静态成员函数不能是虚函数(无
this指针,无法访问 vptr,无法动态绑定)。 - 构造函数不能是虚函数(对象构造时 vptr 尚未初始化,无法访问 vtable)。
- 内联函数可以是虚函数,但触发动态绑定时,内联优化失效(内联是编译期,动态绑定是运行期)。
- 友元函数不能是虚函数(友元不是类成员,无
this指针,无法关联 vtable)。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论

