C++ 基础继承:从概念到实践的全面指南

在抽象层面讨论过继承之后,本节将阐述其在 C++ 中的具体用法。

在 C++ 中,继承发生于类之间。于继承(is-a)关系中,被继承的类称为父类(parent class)、基类(base class)或超类(superclass);实施继承的类称为子类(child class)、派生类(derived class)或子类(subclass)。

Code::Blocks安装

在上图中,Fruit 为父类,Apple 与 Banana 均为子类。

Code::Blocks安装

在此图中,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 有三种方案:

  1. 直接在 BaseballPlayer 类中添加 name 与 age 成员。这是最劣选项,会导致重复代码;若 Person 发生变更,亦需在 BaseballPlayer 中相应修改。
  2. 使用组合,将 Person 作为 BaseballPlayer 的成员。然而需自问:“棒球选手‘有一个’人吗?”答案是否定的,故组合并非恰当范式。
  3. 令 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}
    {
    }
};

用派生图表示,继承关系如下:

Code::Blocks安装

继承后,BaseballPlayer 获得 Person 的成员函数与变量,并额外定义两个自有成员:m_battingAveragem_homeRuns(这些属性专属于棒球选手,而非任意人)。

因此,BaseballPlayer 对象共含 4 个成员变量:m_battingAveragem_homeRuns(来自 BaseballPlayer),以及 m_namem_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_namem_age 及两个访问函数,并额外定义两个成员变量与一个成员函数。注意 printNameAndSalary() 既使用本类成员(Employee::m_hourlySalary),也使用父类成员(Person::m_name)。

派生图如下:

Code::Blocks安装

注意: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]{};
};

派生图如下:

Code::Blocks安装

所有 Supervisor 对象均继承 Employee 与 Person 的函数和变量,并新增 m_overseesIDs 成员变量。

通过构建此类继承链,可创建一组可复用的、自上而下由通用到具体的类。

为何如此继承有益?

继承基类意味着无需在派生类中重新定义基类信息;通过继承自动获得基类成员函数与成员变量,仅需追加所需额外函数或变量。这不仅减少工作量,也意味着若将来更新或修改基类(如新增函数或修复缺陷),所有派生类将自动继承变更!

例如,若向 Person 添加新函数,Employee、Supervisor 与 BaseballPlayer 将自动获得该函数;若向 Employee 添加新变量,Supervisor 亦将自动获得。借此,可轻松、直观且低维护成本地构建新类!

结论

继承让我们通过派生复用类之成员。本章后续将继续深入探讨其机制。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"