在抽象层面讨论过继承之后,本节将阐述其在 C++ 中的具体用法。
在 C++ 中,继承发生于类之间。于继承(is-a)关系中,被继承的类称为父类(parent class)、基类(base class)或超类(superclass);实施继承的类称为子类(child class)、派生类(derived class)或子类(subclass)。
在上图中,Fruit 为父类,Apple 与 Banana 均为子类。
在此图中,Triangle 既是 Shape 的子类,又是 Right Triangle 的父类。
子类从父类继承行为(成员函数)与属性(成员变量)(受本章后续将讨论的访问限制约束),这些变量与函数成为派生类的成员。
由于子类本身是完备的类,其亦可定义专属于该类的成员,稍后将以示例说明。
Person 类
以下为一个表示通用“人”的简单类:
#include <string>
#include <string_view>
class Person
{
// 本例为简化起见,将所有成员设为 public
public:
std::string m_name{};
int m_age{};
Person(std::string_view name = "", int age = 0)
: m_name{ name }, m_age{ age }
{
}
const std::string& getName() const { return m_name; }
int getAge() const { return m_age; }
};
因该类用于表示通用“人”,故仅定义任何类型的人都共有的成员:姓名与年龄。
注意:出于示例简洁,所有变量与函数均设为 public;正常情况下应将其设为 private。继承与访问控制将在本章后续讨论。
BaseballPlayer 类
假设需编写程序记录若干棒球选手的信息。棒球选手需存储专属于棒球的数据,例如打击率与全垒打数。
以下为不完整的 BaseballPlayer 类:
class BaseballPlayer
{
// 本例为简化起见,将所有成员设为 public
public:
double m_battingAverage{};
int m_homeRuns{};
BaseballPlayer(double battingAverage = 0.0, int homeRuns = 0)
: m_battingAverage{battingAverage}, m_homeRuns{homeRuns}
{
}
};
此外,还需记录棒球选手的姓名与年龄,而 Person 类已包含这些信息。
将姓名与年龄加入 BaseballPlayer 有三种方案:
- 直接在 BaseballPlayer 类中添加 name 与 age 成员。这是最劣选项,会导致重复代码;若 Person 发生变更,亦需在 BaseballPlayer 中相应修改。
- 使用组合,将 Person 作为 BaseballPlayer 的成员。然而需自问:“棒球选手‘有一个’人吗?”答案是否定的,故组合并非恰当范式。
- 令 BaseballPlayer 从 Person 继承这些属性。继承刻画 is-a 关系;棒球选手“是一个”人吗?是的,因此继承是正确选择。
将 BaseballPlayer 设为派生类
令 BaseballPlayer 继承自 Person 的语法十分简洁:在类 BaseballPlayer 声明后使用冒号、关键字 public
及欲继承的类名。此方式称为公有继承(public inheritance),其含义将在本章后续详述。
// BaseballPlayer 公有继承自 Person
class BaseballPlayer : public Person
{
public:
double m_battingAverage{};
int m_homeRuns{};
BaseballPlayer(double battingAverage = 0.0, int homeRuns = 0)
: m_battingAverage{battingAverage}, m_homeRuns{homeRuns}
{
}
};
用派生图表示,继承关系如下:
继承后,BaseballPlayer 获得 Person 的成员函数与变量,并额外定义两个自有成员:m_battingAverage
与 m_homeRuns
(这些属性专属于棒球选手,而非任意人)。
因此,BaseballPlayer 对象共含 4 个成员变量:m_battingAverage
与 m_homeRuns
(来自 BaseballPlayer),以及 m_name
与 m_age
(来自 Person)。
验证如下:
#include <iostream>
#include <string>
#include <string_view>
class Person
{
public:
std::string m_name{};
int m_age{};
Person(std::string_view name = "", int age = 0)
: m_name{name}, m_age{age}
{
}
const std::string& getName() const { return m_name; }
int getAge() const { return m_age; }
};
// BaseballPlayer 公有继承自 Person
class BaseballPlayer : public Person
{
public:
double m_battingAverage{};
int m_homeRuns{};
BaseballPlayer(double battingAverage = 0.0, int homeRuns = 0)
: m_battingAverage{battingAverage}, m_homeRuns{homeRuns}
{
}
};
int main()
{
// 创建 BaseballPlayer 对象
BaseballPlayer joe{};
// 直接为其赋值姓名(因 m_name 为 public)
joe.m_name = "Joe";
// 输出姓名
std::cout << joe.getName() << '\n'; // 使用从 Person 继承的 getName()
return 0;
}
运行输出:
Joe
编译运行成功,因为 joe
是 BaseballPlayer,而所有 BaseballPlayer 对象均通过继承拥有 m_name
成员变量与 getName()
成员函数。
Employee 派生类
再编写一个同样继承自 Person 的类:Employee
。雇员“是一个”人,故使用继承恰当:
// Employee 公有继承自 Person
class Employee: public Person
{
public:
double m_hourlySalary{};
long m_employeeID{};
Employee(double hourlySalary = 0.0, long employeeID = 0)
: m_hourlySalary{hourlySalary}, m_employeeID{employeeID}
{
}
void printNameAndSalary() const
{
std::cout << m_name << ": " << m_hourlySalary << '\n';
}
};
Employee 从 Person 继承 m_name
、m_age
及两个访问函数,并额外定义两个成员变量与一个成员函数。注意 printNameAndSalary()
既使用本类成员(Employee::m_hourlySalary
),也使用父类成员(Person::m_name
)。
派生图如下:
注意:Employee 与 BaseballPlayer 之间无直接关联,尽管二者皆继承自 Person。
完整示例:
#include <iostream>
#include <string>
#include <string_view>
class Person
{
public:
std::string m_name{};
int m_age{};
Person(std::string_view name = "", int age = 0)
: m_name{name}, m_age{age}
{
}
const std::string& getName() const { return m_name; }
int getAge() const { return m_age; }
};
// Employee 公有继承自 Person
class Employee: public Person
{
public:
double m_hourlySalary{};
long m_employeeID{};
Employee(double hourlySalary = 0.0, long employeeID = 0)
: m_hourlySalary{hourlySalary}, m_employeeID{employeeID}
{
}
void printNameAndSalary() const
{
std::cout << m_name << ": " << m_hourlySalary << '\n';
}
};
int main()
{
Employee frank{20.25, 12345};
frank.m_name = "Frank"; // 因 m_name 为 public,可直接赋值
frank.printNameAndSalary();
return 0;
}
输出:
Frank: 20.25
继承链(Inheritance chains)
亦可自一个本身又派生自其他类的类进行继承,此过程中并无特殊之处,一切按前述方式展开。
例如,编写 Supervisor 类:主管“是一个”雇员,而雇员“是一个”人。已有 Employee 类,故以其为基类派生 Supervisor:
class Supervisor: public Employee
{
public:
// 该主管最多监管 5 名员工
long m_overseesIDs[5]{};
};
派生图如下:
所有 Supervisor 对象均继承 Employee 与 Person 的函数和变量,并新增 m_overseesIDs
成员变量。
通过构建此类继承链,可创建一组可复用的、自上而下由通用到具体的类。
为何如此继承有益?
继承基类意味着无需在派生类中重新定义基类信息;通过继承自动获得基类成员函数与成员变量,仅需追加所需额外函数或变量。这不仅减少工作量,也意味着若将来更新或修改基类(如新增函数或修复缺陷),所有派生类将自动继承变更!
例如,若向 Person 添加新函数,Employee、Supervisor 与 BaseballPlayer 将自动获得该函数;若向 Employee 添加新变量,Supervisor 亦将自动获得。借此,可轻松、直观且低维护成本地构建新类!
结论
继承让我们通过派生复用类之成员。本章后续将继续深入探讨其机制。