C++进阶:继承

文章目录

构建复杂类有两种方式, 一种是对象组合, 另一种是继承.

继承:

为什么要用到继承呢? 因为它更省事, 就好像是我们不用在去发现一遍牛顿定理, 而是直接使用它的理论就行.

继承也是如此, 在涉及第三方类的时候, 无需重新实现一遍, 只要继承它, 使用我们用到的功能就行, 这提高了开发效率.

继承在现实生活中也随处可见。当我们出生时,我们继承了父母的基因,并从他们身上获得了身体属性------但随后我们又在此基础上添加了自己的个性。

而父母就是基类, 我们就是子类, 继承关系可以表示为子类指向父类的一个箭头, 表示子类引用父类(而不是相反)

C++ 中的继承发生在类之间。在继承关系中,被继承的类称为父类、基类或超类,而进行继承的类称为子类、派生类或子类。

父类(parent class),

也叫基类(base class), 超类(superclass)

cpp 复制代码
#include <string>
#include <string_view>

class Persion
{
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; }
};

继承(inheritance)

现在让我们编写一个也继承自 Person 的类-------Employee 类。员工"就是"人,因此使用继承是合适的:

子类(child class)

也叫派生类, 或者衍生类(derived class),子类 (subclass)

cpp 复制代码
class Employee:public Person
{
public:
    double m_hourlySalary{};
    long m_employyeID{};

    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 的完整示例:

cpp 复制代码
#include <string_view>

class Persion
{
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; }
};

class Employee:public Person
{
public:
    double m_hourlySalary{};
    long m_employyeID{};

    Employee(double hourlySalary = 0.0, long employeeID = 0)
        :m_hourlySalary{ hourlySalary }
        ,m_employeeID{ employeeID }
    {
    }

    void printNameAndSalary() const
    {
        std::cout << m_name << ": " << m_hourlySalary << '\n';
    }
};

我们再编写一个Manager类, 继承Employee, 类层次表示为经理也是一个员工

cpp 复制代码
#include <string>
#include <string_view>
#include <list>
class Persion
{
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; }
};

class Employee:public Person
{
public:
    double m_hourlySalary{};
    long m_employyeID{};

    Employee(double hourlySalary = 0.0, long employeeID = 0)
        :m_hourlySalary{ hourlySalary }
        ,m_employeeID{ employeeID }
    {
    }

    void printNameAndSalary() const
    {
        std::cout << m_name << ": " << m_hourlySalary << '\n';
    }
};

class Manager : public Employee
{
    list<Employee*> m_group{};// 所管理的员工
    short m_level{};          // 职称等级
};

继承链

像上面这种经理就是员工, 员工就是人, 从而形成了一条 "经理->员工->人" 的链, 叫做继承链.

所有 Manager 对象都继承了 Employee 和 Person 的函数和变量,并添加了自己的 m_group , m_level成员变量。

通过构建这样的继承链,我们可以创建一组可重用的类,这些类非常通用(在顶部),并且在每个继承级别上变得越来越具体。

多继承(菱形继承)

在涉及继承链中, 总会遇到一个金典的错误, 就是菱形继承;

在调用name的时候,编译器不知道是从D->B->A过来的, 还是D->C->A过来的, 所以发生了二义性错误, 这种情况下只需加上限定类就可以了, 比如d.B::name 或者d.C::name都可以.

子类是怎么继承的?

首先,让我们介绍一些新的类,它们将帮助我们说明一些要点。

cpp 复制代码
#include <iostream>
class Base
{
public:
    int m_id {};

    Base(int id=0)
        :m_id {id}
    {
    }

    int getId() const { return m_id; }
};

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0)
        :m_cost { cost }
    {
    }

    double getCost() const { return m_cost; }
};

那么我们会问到底继承了什么?

  • A, 是子类把基类的数据成员 和给自己拷贝一份吗?

  • B,还是有两部分, 一部分为Base, 另一部分为Derived?

请选择你的猜想? 选择A 还是B?

cpp 复制代码
int main()
{
    Derived d;

    return 0;
}

为了能够显示正确的过程, 我们在初始化器中加上可以辨别的输出语句, 来测试一下?

以下是子类实例化时实际发生的情况:

  • 为子类部分预留内存(足够用于基础部分和子类部分)
  • 调用适当的 Derived 构造函数
  • 首先使用适当的 Base 构造函数构造 Base 对象。如果没有指定基构造函数,则将使用默认构造函数。
  • 成员初始化列表初始化变量
  • 构造函数主体执行
  • 控制权返回给调用者

注意 :在 Derived 构造函数执行任何实质性操作之前,Base 构造函数会先被调用。Base 构造函数设置对象的 Base 部分,然后控制权返回给 Derived 构造函数,最后 Derived 构造函数才能完成其工作。

所以正确答案是B

如何在初始化基类?

在上面的子类Derived中我们想初始化基类Base, 该如何做呢?

尝试一下如下面这么做会怎么样?

cpp 复制代码
 Derived(double cost=0.0, int id = 5)//// 错误!不能在派生类中直接初始化基类成员
        :m_cost { cost }
        ,m_id { id }
    {
        std::cout << "Derived\n";
    }

经了解, 类的构造函数初始化列表只能初始化:

  • 派生类自己的非静态数据成员
  • 直接基类(通过调用基类构造函数)

但我们如果这样写呢?

cpp 复制代码
 Derived(double cost=0.0, int id = 5)
        :m_cost { cost }
        ,Base { id }            // 回归注意内容: 它的初始化顺序为Base->Derived, 先有子类, 后有父类, 子类是建立在父类的基础上的
    {
        std::cout << "Derived\n";
    }

修改初始化顺序就可以了

cpp 复制代码
 Derived(double cost=0.0, int id = 5)
        :Base { id }
        ,m_cost { cost }
    {
        std::cout << "Derived\n";
    }

当然还可以这样写也没问题.

cpp 复制代码
 Derived(double cost=0.0, int id = 5)
        :m_cost { cost }
    {
        m_id = id; // √
        std::cout << "Derived\n";
    }

访问控制

坑踩完了, 上面这些例子都是建立在基类的public数据成员公有继承方式的基础上的, 但实际上数据成员为private 和protected. 关于这里的内容, 就涉及到了访问控制的知识, 完整如下:

一个类成员可以是private, protected, public的:

  • 如果它是private的, 仅被所属类的成员函数和友元函数所使用
  • 如果它是protected的, 仅被所属类的成员函数和友元函数以及派生类的成员函数和友元函数所使用
  • 如果它是public的, 可以被任何函数所使用.

这反映了函数按类访问权限可分为三类:

  • 实现类的函数(其友元和成员)
  • 实现派生类的函数(派生类的函数(派生类的友元和成员)
  • 其它函数

而派生类和基类的关系就只涉及purblic, 和 protected, 且只有派生类单向访问基类, 所以下来就共有6种

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的provate成员
基类的protected成员 派生类的protected成员 派生类的protected成员函数 派生类provate成员

为什么要取消派生类成员对基类的private成员的访问呢?

答:如果派生类的成语函数刻意访问其基类的私有成员, 这会令私有成员的概念变得毫无意义, 因为程序员简单地从一个类派生出一个新类, 就能获得其私有部分的访问权. 而且我们再也不能通过检查成员函数和友元函数就找到私有成员的使用之处了. 我们必须检查完整程序中涉及派生类的所以源文件, 然后检查这些类的每个函数, 然后再检查这些类的所以派生类, 依次类推. 这样的方式不仅是一项烦人的工作, 而且实际上通常是行不通的. 如果可行, 我们可以使用保护的而非私有的成员.

如果您不选择继承方式,C++ 默认为私有继承(就像如果您不另

行指定,成员默认为私有访问一样)。

接下来我们可以测试验 证一下, 还是上面的测试方法: 我们先选择好继承方式和所访问的基类成员, 然后在子类中给父类的成员赋值, 看是否成功, 如果可以成功就说名可以访问:

还有自身成员函数是可以访问其所在类所以成员的, 而初始化器函数就是自身的成员函数, (没必要protected和public各自定义一个初始化器, 会报重复声明的错误)

结合这两点, 我们可以设计一个基类: 一个protected成员变量, 一个public成员变量. 一个子类:一个protected成员变量, 一个public成员变量; 然后利用初始化器函数子类成员换着给基类的变量赋值就行;

(1)第一组public继承:

类成员/继承方式 public继承 是否完成
基类的public成员 派生类的public成员函数
基类的protected成员 派生类的protected成员函数

初始化顺序错, 在类中成员变量谁先声明谁先初始化:

子类先声明的m_sub_pro, 然后是m_sub_pub ,所以初始化顺序也是这个m_sub_pro, m_sub_pub. 还有赋值的形参顺序.

正确代码如下:

cpp 复制代码
#include <iostream>
class Base
{
protected:
    int m_pro {};
public:
    int m_pub {};

    Base(int pro=0, int pub=0)
        :m_pro{pro}
        ,m_pub{pub}
    {
        std::cout << "Base:: " << m_pro << " : " << m_pub << '\n';
    }
    void print()
    {
        std::cout << m_pro << " : " << m_pub << '\n';
    }
};

class Derived : public Base
{
protected:
    int m_sub_pro { };
public:
    int m_sub_pub { };
    Derived(int subpro=0, int subpub=0)
        :m_sub_pro{subpro}
        ,m_sub_pub{subpub}
    {
        m_pub = m_sub_pub;
        m_pro = m_sub_pro;
    }
};

int main()
{
    Derived d;

    return 0;
}

检查基类的初始值, 对;

派生类public成员 ---->基类public成员 对

派生类protected成员 ---->基类protected成员 对

cpp 复制代码
int main()
{
    //Derived d;
    Derived d(1, 2);
    d.print();

    return 0;
}

派生类public成员 ---->基类protected成员

派生类protected成员 ---->基类public成员

结论:

(2)第一组protected继承:

类成员/继承方式 public继承 是否完成
基类的public成员 派生类的public成员
基类的protected成员 派生类的protected成员


为了防止之前的可执行文件没清理而影响, 我又删除了可执行文件

先执行一遍, 确保没缓存影响


结论:可能是由于其他地方或者设计不合理, 测失败.

我把子类的public成员改成protected了

最后发现::错误!main()不是派生类,不能调用protected构造函数

这说明需要一个设计一个派生类去调用它, 但所有的函数要经过main函数, 我不会设计了, 暂时放弃了.
结论 :缺乏验证的手段, 待定

(3) private继承

原因同上, 待定.

再学习着找方法吧

相关推荐
赵杰伦cpp4 小时前
数据结构——二叉搜索树深度解析
开发语言·数据结构·c++·算法
扫地的小何尚4 小时前
一小时内使用NVIDIA Nemotron创建你自己的Bash计算机使用智能体
开发语言·人工智能·chrome·bash·gpu·nvidia
MoonBit月兔5 小时前
MoonBit Pearls Vol.12:初探 MoonBit 中的 JavaScript 交互
开发语言·javascript·数据库·交互·moonbit
第七序章5 小时前
【C + +】unordered_set 和 unordered_map 的用法、区别、性能全解析
数据结构·c++·人工智能·算法·哈希算法·1024程序员节
草莓熊Lotso5 小时前
《算法闯关指南:优选算法--二分查找》--23.寻找旋转排序数组中的最小值,24.点名
开发语言·c++·算法·1024程序员节
foundbug9995 小时前
C# 实现 Modbus TCP 通信
开发语言·tcp/ip·c#
郝学胜-神的一滴5 小时前
主成分分析(PCA)在计算机图形学中的深入解析与应用
开发语言·人工智能·算法·机器学习·1024程序员节
JuicyActiveGilbert5 小时前
【Python进阶】第2篇:单元测试
开发语言·windows·python·单元测试
sulikey6 小时前
Qt 入门简洁笔记:信号与槽
前端·c++·笔记·qt·前端框架·1024程序员节·qt框架