• c++ 类的继承(一)


    1. 简介

    继承是一种面向对象编程的重要特性,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为。基类中定义的成员变量和成员函数可以在派生类中使用,从而实现代码重用和层次化设计。

    1.1 是否能继承

    并不是所有的类都能存在继承关系。必须满足: is a 判定
    大千世界,不是什么东西都能产生继承关系。只有存在某种联系,才能表示有继承关系。如:哺乳动物是动物,猫是哺乳动物,因此,猫是动物,等等。所以在学习继承后面的内容之前,先说说两个术语 is Ahas A

    1.1.1 is A

    is A是一种继承关系,指的是类的父子继承关系。表达的是一种方式:这个东西是那个东西的一种。例如:西红柿是蔬菜的一种。 西红柿:子类,蔬菜:父类

    1.1.2 has A

    has a 是一种组合关系,是关联关系的一种(一个类中有另一个类型的实例),是整体和部分之间的关系(比如电脑和GPU之间),并且代表的整体对象负责构建和销毁部分对象,代表部分的对象不能共享。

    2. 特点

    1. 代码重用:通过继承,派生类可以获得基类的成员变量和成员函数,避免了重复编写相同的代码。
    2. 层次化设计:通过继承,可以构建对象之间的层次化关系,形成类的继承体系。派生类可以进一步扩展和特化基类的功能。
    3. 隐藏和覆盖:派生类可以隐藏基类中的成员函数(包括构造函数和析构函数)和成员变量,并且可以重新定义(覆盖)基类中的虚函数,以实现多态性。
    4. 多态性:通过基类的指针或引用,可以在运行时动态地调用派生类的成员函数,实现多态性的效果。
      代码:
    #include 
    #include 
    
    using namespace std;
    
    //父类
    class Vegetable{
    public:
        string name;
        bool delicious;
    };
    
    
    //子类
    class Cabbage:public Vegetable{
    
    };
    
    
    int main(){
    
        //子类虽然没有声明name 和 delicious ,但是继承了Vegetable类,等同于自己定义的效果一样
        Cabbage ca;
        ca.name = "Chinese cabbage";
        ca.delicious = true;
    
        cout << ca.name << "  " << ca.delicious << 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

    3. 继承访问权限

    当一个类派生自基类,该基类可以被继承为 public、protectedprivate 几种类型。继承类型是在继承基类时指定的。 如: class DerivedClass : public BaseClass。 我们几乎不使用 protectedprivate 继承,通常使用 public继承。

    • public

    表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口

    • private

    表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;

    • protected

    表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。

    1. 公有继承(public)
      基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
    #include 
    
    using namespace std;
    
    class BaseClass{
    public:
        string name;
    protected:
        int no;
    private:
        int num;
    
    };
    
    class DerivedClass: public BaseClass{
        void print(){
            name = "aili";
            no = 2;
            num = 45; //编译错误! 无法访问 num
        }
    };
    
    int main(){
    
        DerivedClass de;
        de.name = "baidu" ; 
        de.no = 12;   //编译错误! 无法访问 no
        de.num = 18 ; //编译错误! 无法访问 num
    
        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
    1. 私有继承(private)
      基类所有成员在派生类中的访问权限都会变为私有(private)权限;
    #include 
    
    using namespace std;
    
    class BaseClass{
    public:
        string name;
    protected:
        int no;
    private:
        int num;
    
    };
    
    class DerivedClass: private BaseClass{
        void print(){
            name = "aili";
            no = 2;
            num = 45; //编译错误! 无法访问 num
        }
    };
    
    int main(){
    
        DerivedClass de;
        de.name = "baidu" ;//编译错误! 无法访问 name
        de.no = 12;   //编译错误! 无法访问 no
        de.num = 18 ; //编译错误! 无法访问 num
    
        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
    1. 保护继承(protected)
      除了private还是私有的属性之外,其他的属性都变成了protected,在子类中具有访问权限,但是在类的外面则无法访问。
    #include 
    
    using namespace std;
    
    class BaseClass{
    public:
        string name;
    protected:
        int no;
    private:
        int num;
    
    };
    
    class DerivedClass: protected BaseClass{
        void print(){
            name = "aili";
            no = 2;
            num = 45; //编译错误! 无法访问 num
        }
    };
    
    int main(){
    
        DerivedClass de;
        de.name = "baidu" ;//编译错误! 无法访问 name
        de.no = 12;   //编译错误! 无法访问 no
        de.num = 18 ; //编译错误! 无法访问 num
    
        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

    4. 继承关系下的构造函数的运行顺序

    构造函数是对象在创建时调用,析构函数是对象在销毁时调用。但是在继承关系下,无论在对象的创建还是销毁,都会执行父类和子类的构造和析构函数。 它们一般会有以下规则:

    1. 子类对象在创建时会首先调用父类的构造函数;父类构造函数执行完毕后,执行子类的构造函数;
    2. 当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;
    3. 析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数
    1. 子类对象在创建时会首先调用父类的构造函数,父类构造函数执行完毕后,执行子类的构造函数
    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        Base(){
            cout<< "基类的构造..." <<endl;
        }
    };
    
    
    class Derived : public Base {
    public:
        Derived(){
            cout<< "派生类的构造..." <<endl;
        }
    };
    
    int main() {
    
        Base b ;
    
        cout << "------------------------" << endl;
    
        Derived d;
        
        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

    运行结果:

    基类的构造...
    ------------------------
    基类的构造...
    派生类的构造...
    
    • 1
    • 2
    • 3
    • 4

    创建派生类对象的时候,会先执行基类的构造,然后在执行派生类的构造。
    因为派生类里面有可能使用到了基类的成员(例如指针成员),这些成员应该在基类先完成初始化,然后派生类才能使用他们。

    1. 子类析构函数执行完毕后,执行父类的析构造函数
    #include 
    
    using namespace std;
    
    class Base{
    public:
        Base(){
            cout<< "基类的构造..." <<endl;
        }
        ~Base(){
            cout<< "基类的析构..." <<endl;
        }
    };
    
    
    class Derived : public Base {
    public:
        Derived(){
            cout<< "派生类的构造..." <<endl;
        }
        ~Derived(){
            cout<< "派生类的析构..." <<endl;
        }
    };
    
    int main() {
    
        //Base b ;
    
        cout << "------------------------" << endl;
    
        Derived d;
    
        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

    运行结果:

    ------------------------
    基类的构造...
    派生类的构造...
    派生类的析构...
    基类的析构...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    先执行子类的析构,然后在执行父类的析构
    确保子类完全释放了之后,再走父类的释放回收工作,有可能子类里里面使用到了父类的一些成员。
    3. 继承和组合
    如果在继承状态下,子类中的成员又含有其他类的对象属性,那么他们之间的构造很析构调用顺序,遵循以下原则:

    a. 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;
    b. 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。
    代码:

    #include 
    
    using namespace std;
    
    class Base{
    public:
        Base(){
            cout<< "基类的构造..." <<endl;
        }
        ~Base(){
            cout<< "基类的析构..." <<endl;
        }
    };
    class Other{
    public:
        Other(){
            cout<< "Other的构造..." <<endl;
        }
        ~Other(){
            cout<< "Other的析构..." <<endl;
        }
    };
    
    class Derived : public Base {
    public:
        Other o;
        Derived(){
            cout<< "派生类的构造..." <<endl;
        }
        ~Derived(){
            cout<< "派生类的析构..." <<endl;
        }
    };
    
    int main() {
    
        //Base b ;
    
        cout << "------------------------" << endl;
    
        Derived d;
    
        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

    运行结果:

    ------------------------
    基类的构造...
    Other的构造...
    派生类的构造...
    派生类的析构...
    Other的析构...
    基类的析构...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5. 调用父类有参构造

    继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。
    在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。c++ 使用初始化列表来完成这个工作

    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        string name;
        int num;
        Base(){
            cout<< "基类的无参构造..." <<endl;
        }
        Base(string name,int num){
            cout<< "基类的有参构造..." <<endl;
        }
    
    };
    
    
    class Derived : public Base {
    public:
    
        Derived(){
            cout<< "派生类的无参构造..." <<endl;
        }
        Derived(string name,int num){
            cout<< "派生类的有参构造..." <<endl;
        }
    };
    
    int main() {
    
    
    
        Derived d;
    
        cout << "------------------------" << endl;
    
        Derived d1("libai",24);
    
    
        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

    运行结果:

    基类的无参构造...
    派生类的无参构造...
    ------------------------
    基类的无参构造...
    派生类的有参构造...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    默认情况下,子类的所有构造永远会调用父类的无参构造。如果父类没有无参构造,只有有参构造

    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        string name;
        int num;
        Base(){
            cout<< "基类的无参构造..." <<endl;
        }
        Base(string name,int num){
            cout<< "基类的有参构造..." <<endl;
        }
    
    };
    
    
    class Derived : public Base {
    public:
    
        Derived(){
            cout<< "派生类的无参构造..." <<endl;
        }
        Derived(string name,int num):Base(name,num){
            cout<< "派生类的有参构造..." <<endl;
        }
    };
    
    int main() {
    
    
    
        Derived d;
    
        cout << "------------------------" << endl;
    
        Derived d1("libai",24);
    
    
        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

    运行结果:

    基类的无参构造...
    派生类的无参构造...
    ------------------------
    基类的有参构造...
    派生类的有参构造...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建子类对象,必须要先走父类的构造函数,才能走子类的构造函数
    在子类里面手动调用父类的有参构造,需要使用初始化列表的方式来完成,初始化列表的工作是在子类构造函数体调用之前就执行了。

  • 相关阅读:
    mysql存储过程标准模板
    Java开发学习----Spring事务属性、事务传播行为
    企业对CMMI认证存在的误区有哪些?
    将Excel中的数据导入shell脚本,并调用expect脚本
    专用神经网络处理器芯片,神经网络芯片的单片机
    深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml【第79篇—读写XML文件】
    请问哪家淘宝/天猫店 您家的是真的 STM32F103C8T6系统板?
    从「百亿美金」跌落,这家自动驾驶公司落寞背后的故事
    局部指令和全局指令的注册和使用
    Linux-CentOS8-Oracle19c 安装详解
  • 原文地址:https://blog.csdn.net/weixin_40378209/article/details/133876626