• 嵌入式养成计划-40----C++菱形继承--虚继承--多态--模板--异常


    九十四、菱形继承

    94.1 概念

    • 菱形继承又称为钻石继承,
    • 是由公共基类派生出多个中间子类,又由中间子类共同派生出汇聚子类,
    • 汇聚子类会得到多份中间子类从公共基类继承下来的数据成员,会造成空间浪费,没有必要。

    所以存在一个问题:

    • 汇聚子类会得到多份中间子类从公共基类继承下来的数据成员,会造成空间浪费,没有必要。
    • 会多次对公共基类的数据成员初始化,或者释放。
    • 如何避免?

    94.2 形式

       A     --------公共基类
     /   \
    B     C   -------中间子类
     \   /
       D     --------汇聚子类
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 上面那个问题的解决方法:虚继承

    九十五、虚继承

    95.1 作用

    • 使汇聚子类仅获得一份经中间子类从公共基类继承下来的数据成员。

    95.2 格式

    • 关键字 :virtual
    • 在中间子类的继承方式前加 virtual
    class 类名:virtual 继承方式 类名   //中间子类,可多继承
    {
    	中间子类自己的内容;
    };
    
    • 1
    • 2
    • 3
    • 4

    95.3 注意

    • 虚继承之后,只保留一份中间子类从公共基类继承下来的数据成员,
    • 但是不知道保留哪个中间子类的,所以就会自动调用公共基类的无参构造函数,
    • 如果想使用公共基类的有参构造函数,则需要在汇聚子类中调用公共基类的有参构造函数。

    九十六、多态

    • 静态多态(在编译时加载)—> 如 :函数重载
    • 动态多态(在运行时加载)

    96.1 啥是多态

    • 多态 :一种形式 拥有 多种状态
    • 例如 :一个人,在不同环境下有着不同的状态,也有不同的 属性 和 功能

    • 多态:父类的指针或者引用,指向或者初始化子类的对象,调用子类对父类重写的函数,进而使用子类的功能。

    96.2 函数重写

    • 要求 :
      1. 两个类之间必须要有继承关系
      2. 子类和父类有同名同类型的函数
      3. 父类中的该函数必须是虚函数

    96.3 虚函数

    • 关键字 :virtual
    1. 在函数前加 virtual ----->虚函数
    2. 虚函数满足继承,
      如果父类中函数是虚函数,那么继承到子类中,该函数还是虚函数,
      如果子类继续被继承,那么“孙类”中的该函数还虚函数…

    96.4 赋值兼容规则

    • 父类的指针或者引用,可以指向或者初始化子类的对象
    • 父类指针指向的仅仅只是子类中继承父类的那段空间
      在这里插入图片描述

    96.5 多态中,实现函数重写的原理

    1. 类中有虚函数时,虚函数都会有一个虚指针
    2. 虚指针在类的最前面,指向了虚函数表,虚函数表里记录虚函数
    3. 虚指针和虚函数表是实现多态的重要机制
      在这里插入图片描述

    96.6 虚析构函数

    • 因为父类指针指向子类对象,只作用与子类从父类继承下来的那片特殊空间,
    • 释放父类指针,只会把父类指针作用的那块空间释放,子类自己拓展的空间没有得到释放,从而造成内存泄漏。

    虚析构函数 :如果把父类中析构函数设置成虚析构函数,那么子类拓展的空间就会被一起释放,虚析构函数也满足继承。

    示例 :

    #include 
    using namespace std;
    
    class Person
    {
    private:
        string name;
    public:
        Person() {}
        Person(string name):name(name){}
        virtual ~Person(){}  //虚析构函数
    };
    class Stu:public Person
    {
    private:
        int id;
    public:
        Stu() {}
        Stu(string n, int id):Person(n),id(id){}
        ~Stu(){}
    };
    int main()
    {
        Person *ptr = new Stu("zhangsan", 1001);
        delete ptr;  //如果没有虚析构函数的话,只释放父类指针作用的空间,子类
                     //拓展的空间并没有得到释放,会造成内存泄漏。解决方案:虚析构函数
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    96.7 纯虚函数

    • 当父类中的虚函数只用来被子类重写,并且没有需要去完成的功能,那么一般将该虚函数设置成纯虚函数。

    • 格式:

      virtual 函数返回值类型 函数名(形参列表) = 0 ; //纯虚函数
      //纯虚函数 是在父类中声明,子类中实现
      
      • 1
      • 2

      在这里插入图片描述

    96.8 抽象类

    • 概念: 抽象类中至少有一个纯虚函数,抽象类不能具体的实例化一个对象,一般是用来被继承的。
      不能实例化对象,只能执行子类对象

    • 如果父类中有纯虚函数,表示父类是抽象类,
      子类继承后,如果没有对父类中纯虚函数做重写,则子类也是一个抽象类,不能实例化一个对象。

    例如 :

    #include 
    using namespace std;
    
    class A
    {
    private:
        int a;
    public:
        virtual void show() = 0;//纯虚函数  
    };
    
    class B :public A
    {
    private:
        int b;
    public:
    };
    
    int main()
    {
        //B a;  不能实例化一个对象
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    96.9 C++中虚函数与纯虚函数的区别

    1. 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。

    2. 虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义。

    3. 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。

    4. 虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。

    5. 虚函数的定义形式:virtual{};纯虚函数的定义形式:virtual { } = 0;在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数生命周期也不一样。

    九十七、模板

    • 模板就是建立一个通用的模具,大大提高代码的复用性。
    • C++除面向对象编程思想外,还有另一种编程思想,泛型编程,主要利用的技术是 模板
    • C++提供了两种重要的模板机制:函数模板 和 类模板

    生活中的模板 :
    在这里插入图片描述
    在这里插入图片描述

    97.1 模板的特点

    1. 模板是通用的,不是万能的
    2. 模板只是一个框架

    97.2 函数模板

    97.2.1 作用

    • 函数模板,就是建立一个通用的函数,
    • 其返回值类型,或者参数类型不具体制定,用一个虚拟的类型来代替。

    97.2.2 格式

    template<typename T>
    函数的定义
    
    • 1
    • 2

    如 :

    template <typename T>
    T fun(T x, T y)  //建立了一个通用的函数,实现数据类型之和
    {
    	return x+y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    template -------> 创建模板
    typename -------> 表明其后是一种数据类型,typename还可以用class代替
    T -----> 表示数据类型,也可以用其他代替

    调用时 :

    cout << fun(1,2) << endl;
    cout << fun(1.3,1.4) << endl;
    cout << fun('0', '1') << endl;
    
    • 1
    • 2
    • 3

    97.3 类模板

    97.3.1 作用

    • 建立一个通用的类, 类中的 成员变量 的类型 不具体制定,用一个虚拟类型来代替

    97.3.2 格式

    template<typename T>
    类的定义
    
    • 1
    • 2

    template -------> 创建模板
    typename -------> 表明其后是一种数据类型,typename还可以用class代替
    T -----> 表示数据类型,也可以用其他代替

    九十八、异常

    • 作用 :可以优雅的解决异常

    • 实现步骤

      1. try包裹可能产生异常的地方
      2. 在产生异常的条件下,用 throw抛出异常
      3. try后面的 catch语句中接收异常,并在 catch后的代码块中处理异常

    示例 :

    #include 
    using namespace std;
    
    int fun(int x, int y)
    {
        if(y!=0)
        {
            return x/y;
        }
        else
        {
            throw -1;  //抛出异常
        }
    }
    int main()
    {
        try
        {
            fun(9,0); //把可能发生异常的地方用try包裹起来
    
            cout << "hello 啊" << endl;        
        }
        catch (int e)
        {
            if(e == -1)
            {
                cout << "分母为0,不合法" << endl;
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    小作业:

    比喻:
    动物园的讲解员和动物表演
    动物园里有一位讲解员,他会为每种动物表演做简单的介绍,如狮子、大象、猴子等。
    
    提示:
    	在这个场景中,我们可以将动物比作是不同的类,而每种动物表演则是类中的函数。
    	讲解员则是一个基类,他可以根据每种动物的特点和表演,进行相应的介绍。
    具体过程如下:
    	定义一个基类 Animal,其中有一个虛函数perform(),用于在子类中实现不同的表演行为。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我写的

    #include 
    using namespace std;
    
    // base_class
    class Animal
    {
    private:
        string name;
    public:
        Animal() {}
        Animal(string name):name(name) {}
        Animal(const Animal &other):name(other.name){}
        Animal &operator=(const Animal &other){
            name = other.name;
            return *this;
        }
        virtual ~Animal(){}
        virtual void perform() = 0;
        string get_name(){
            return this->name;
        }
    };
    
    class Lion:virtual public Animal
    {
    public:
        Lion() {}
        Lion(string name):Animal(name) {}
        Lion(const Lion &other):Animal(other){}
        Lion &operator=(const Lion &other){
            Animal::operator=(other);
            return *this;
        }
        ~Lion(){}
        void perform() {
            cout << Animal::get_name() + " : " << "河东狮吼" << endl;
        }
    };
    
    class Elephant:virtual public Animal
    {
    public:
        Elephant() {}
        Elephant(string name):Animal(name) {}
        Elephant(const Elephant &other):Animal(other){}
        Elephant &operator=(const Elephant &other){
            Animal::operator=(other);
            return *this;
        }
        ~Elephant(){}
        void perform() {
            cout << Animal::get_name() + " : " << "象群践踏" << endl;
        }
    };
    
    class Monkey:virtual public Animal
    {
    public:
        Monkey() {}
        Monkey(string name):Animal(name) {}
        Monkey(const Monkey &other):Animal(other){}
        Monkey &operator=(const Monkey &other){
            Animal::operator=(other);
            return *this;
        }
        ~Monkey(){}
        void perform() {
            cout << Animal::get_name() + " : " << "专业偷桃" << endl;
        }
    };
    
    int main()
    {
        Animal *p = nullptr;
        Lion l("狮子狗");
        Elephant e("孟获");
        Monkey m("孙猴子");
    
        p = &l;
        p->perform();
        p = &e;
        p->perform();
        p = &m;
        p->perform();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    在这里插入图片描述

  • 相关阅读:
    【过程记录】python环境离线迁移
    Unity中OnGUI实时显示游戏FPS的方法
    鸿蒙系统扫盲(一):鸿蒙OS和开源鸿蒙什么关系?
    互联网进入存量博弈时代,小程序技术创造移动应用新机遇
    系统分区
    Linux服务器初始化、yum安装java、redis、mysql
    【ICer必备基础】MOS电容——电容电压特性详解
    神经是怎么传播信息的,神经网络的前向传播
    基于VGG16改进的特征检测器
    SpringMVC调用流程
  • 原文地址:https://blog.csdn.net/qq_52625576/article/details/133797661