继承的基本概念¶
继承是面向对象程序设计的三大核心特征之一(封装、继承、多态)。它既是实现代码复用的强大工具,也是构建可扩展软件体系结构的基石。
从现实世界理解继承¶
在现实世界中,“继承”无处不在:
现实世界的继承关系
- 生物分类:动物是基类,哺乳动物继承自动物,人类继承自哺乳动物。子类拥有父类的全部特征,并发展出自己的独特性。
- 交通工具:车辆是基类,汽车和卡车继承自车辆。所有车辆都有车轮和重量,但汽车有载人数,卡车有载重量。
- 几何图形:形状是基类,圆形、矩形、三角形继承自形状。所有形状都有面积和周长,但计算方式各不相同。
这些例子揭示了一个核心规律:子类继承了父类的全部特征,同时拥有自己独特的属性。这正是面向对象继承思想的现实来源。
继承的哲学本质
继承表达的是 "is-a"(是一种) 关系。卡车是一种车辆,矩形是一种形状。这种关系是永久性的、传递性的——如果卡车是车辆,车辆是交通工具,那么卡车也是交通工具。
继承的定义¶
在面向对象程序设计中,继承构建了类与类之间的"is-a"(是一种) 关系,这些类之间形成了一个类继承体系Inheritance Hierarchy:
- 基类(Base Class):被继承的已有类,也称为父类(Parent Class)。
- 派生类(Derived Class):派生出的新类,也称为子类(Child Class)。
- 直接基类(Direct Base Class):直接参与派生出某类的基类。
- 间接基类(Indirect Base Class):基类的基类,乃至更高层的基类。
直接基类与间接基类
Vehicle(车辆) ← 顶层基类
↑
Car(汽车) ← Vehicle 的直接派生类,也是 SUV 的直接基类
↑
SUV(运动型多功能车) ← Car 的直接派生类,Vehicle 的间接派生类
在上述继承层次中:
- Car 的直接基类是 Vehicle,间接基类不存在(或为 Object,取决于语言)。
SUV的直接基类是Car,间接基类是Vehicle。
单继承派生类的声明¶
在C++中,继承的派生类定义语法如下:
基本示例
关键语法要点
- 冒号
::位于派生类名和基类名之间,标识继承关系的开始。 - 继承方式:
public、protected或private,决定基类成员在派生类中的访问属性。 - 分号:类定义结束时需要分号,与普通类定义相同。
多继承派生类的声明¶
除了单继承之外,在C++中还允许多继承,即派生类从多个基类分别继承。多继承派生类定义语法如下:
多继承的语法结构
关键语法要点
- 逗号:多个基类之间用逗号分隔。
- 继承方式:对于每一个基类都需要单独声明其继承方式,这意味着同一个派生类可以以不同的方式继承不同的基类。
继承的目的¶
1. 实现设计与代码的重用¶
继承最直接的价值是代码复用。派生类无需重新编写基类中已有的代码,只需专注于新增或修改的部分。
代码复用:Vehicle → Car
代码复用的价值
- 减少重复代码:共性的属性和行为只需在基类中定义一次。
- 降低维护成本:修改基类中的共性逻辑,所有派生类自动受益。
- 提高开发效率:开发者可以站在前人的肩膀上,专注于新功能的开发。
2. 实现系统功能的扩展与演化¶
当新的需求出现,原有程序无法满足时,可以通过派生在现有类型的基础上进行扩展。
功能扩展:应对新需求
开闭原则(Open-Closed Principle)
继承是实现面向对象设计原则中开闭原则的重要方式:
- 对扩展开放:通过派生类添加新功能。
- 对修改关闭:不修改已有基类的稳定代码。
这使得软件系统既能适应需求变化,又能保持核心代码的稳定性。
3. 建立类型层次结构¶
继承能够建立类的层次化结构,让不同类型之间形成逻辑关联,便于理解和组织代码。
类型层次结构示例
这种层次结构:
-
表达类型关系:清晰地展示了 "Square is-a Rectangle" 的语义。
-
支持多态:可以通过基类指针统一操作所有派生类对象。
-
便于扩展:新增形状类只需继承
Shape,不影响已有代码。
继承对面向对象程序设计的贡献¶
继承的三大贡献
| 贡献 | 说明 |
|---|---|
| 代码复用 | 避免重复编写相同的代码,提高开发效率和可维护性 |
| 功能扩展 | 在不修改已有代码的前提下,通过派生类添加新功能 |
| 多态基础 | 继承是多态的前提,没有继承就无法实现运行时多态 |
继承与封装的关系
继承不会破坏封装,而是对其的补充:
- 基类的
private成员在派生类中不可直接访问,保护了基类的实现细节。 - 基类的
protected成员在派生类中可直接访问,方便派生类扩展。 - 这种设计平衡了复用需求和封装保护之间的矛盾。
何时应该使用继承?
只有在满足 "is-a"(是一种) 关系时才使用继承:
- ✅
Car是Vehicle的一种 → 使用继承 ✓ - ✅
Circle是Shape的一种 → 使用继承 ✓ - ❌
Car有Engine→ 应使用组合(has-a),而非继承 ✗
错误使用继承会导致设计僵化、难以维护。优先考虑组合,只在确实存在 "is-a" 关系时才使用继承。
一个完整的继承示例:Vehicle → Car → Truck¶
以下示例展示了继承如何实现代码复用和功能扩展:
基类 Vehicle
派生类 Car
派生类 Truck
主函数
运行结果
关键说明
- 构造顺序:先基类(Vehicle),后派生类(Car/Truck)。
- 析构顺序:先派生类(Car/Truck),后基类(Vehicle)。
- 代码复用:Car 和 Truck 都无需重新定义
wheels和weight。 - 功能扩展:Car 增加了
passenger,Truck 增加了passenger和payload。
小结¶
- 继承的本质:保持已有类的特性并构造新类的过程,表达 "is-a" 关系。
- 继承与派生:同一过程的两种视角——继承强调"复用已有",派生强调"新增特性"。
- 继承的目的:
- 实现设计与代码的重用。
- 实现系统功能的扩展与演化。
- 建立类型层次结构,支持多态。
- 继承的价值:
- 减少重复代码,降低维护成本。
- 支持开闭原则(对扩展开放,对修改关闭)。
- 为运行时多态提供基础。
- 使用准则:只有在确实存在 "is-a" 关系时才使用继承;否则优先考虑组合。
继承是面向对象程序设计的纽带,它让类与类之间形成有意义的层次关系,使代码既能复用又能扩展。下一部分将深入探讨不同继承方式对成员访问控制的影响。