• C++超复杂的构造和析构函数执行顺序详解


    几个类的继承关系

    假设有如下代码:

    class A
    {
    public:
        A(){cout<<"A constructed"<<endl;}
        ~A(){cout<<"A delete"<<endl;}
    };
    
    class B:public A
    {
    public:
        B(int v=1):p(v){cout<<"B constructed"<<endl;}
        ~B(){cout<<"B delete"<<endl;}
    
        int p;
    };
    
    class C:public B
    {
    private:
        B b;
    public:
        C(){cout<<"C constructed"<<endl;}
        C(B x):b(x){cout<<"C 的B被赋值"<<endl;}
        ~C(){cout<<"C delete"<<endl;}
    };
    
    
    • 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

    一重继承示例: B b1;

    现在有如下变量创建:

    B b1;
    
    • 1

    我们来看看结果:

    A constructed
    B constructed
    B delete
    A delete
    
    • 1
    • 2
    • 3
    • 4

    这是为什么呢?

    1、构造函数的调用顺序:自上而下
    当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达最底层目标派生类的构造函数。

    也就是说,不过基类有没有成员,都会执行它的构造函数。所以开始是A constructed,其次执行B类自己的构造函数,即:B constructed.

    2、析构函数的调用顺序:自下而上
    当删除一个对象时,首先调用该派生类的析构函数,然后调用上一层基类的析构函数,依次类推,直到到达最顶层的基类析构函数

    故而,删除的时候,从b1这个变量自己开始调用析构函数,即B delete,在调用基类的析构函数,即A delete.

    二重继承示例:C c;

    代码如下:

        C c;
    
    • 1

    结果是:

    A constructed
    B constructed
    A constructed
    B constructed
    C constructed
    C delete
    B delete
    A delete
    B delete
    A delete
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这是为什么呢?

    首先一点,成员初始化的次序是类定义中声明成员的次序

    也就是说,先按声明的顺序,对当前类C的成员变量进行初始化,而当前C的成员变量只有一个即: B b; 由上面的示例我们知道,初始化它的时候,的构造函数的执行顺序是:

    A constructed
    B constructed
    
    • 1
    • 2

    其次,我们需要构造变量C c本身,同样根据上面的示例,知道是:

    A constructed
    B constructed
    C constructed
    
    • 1
    • 2
    • 3

    由此构造函数调用完毕。

    那么析构函数呢,则是跟构造函数相反的方向。先对c变量析构,所以有:

    C delete
    B delete
    A delete
    
    • 1
    • 2
    • 3

    其次对c变量的成员变量 b进行析构,也就是:

    B delete
    A delete
    
    • 1
    • 2

    由此,执行顺序搞定。

    带参数的负责构造: C c(b1);

    假设代码有:

     B b1;
     C c(b1);
    
    • 1
    • 2

    那么其结果是:

    A constructed
    B constructed
    A constructed
    B constructed
    C 的B被赋值
    B delete
    A delete
    C delete
    B delete
    A delete
    B delete
    A delete
    B delete
    A delete
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    咋们来捋一捋:
    首先创建 B b1;对象,有:

    A constructed
    B constructed
    
    • 1
    • 2

    其次,创建 C c(b1);由于带参数,所以不用默认构造函数了,执行带参数的构造函数,所以有:

    A constructed
    B constructed
    C 的B被赋值
    
    • 1
    • 2
    • 3

    来看析构函数,析构函数的执行顺序是,最后一个类变量(这里 是c)先析构,然后在析构b变量;其实就是按在程序当中声明的反顺序来的。

    故而这里先析构c变量:

    C delete
    B delete
    A delete
    
    • 1
    • 2
    • 3

    但是我们发现程序运行结果不是这样,这是为什么呢?

    这是因为需要先析构 C的构造函数当中C(B x) 的B x这个临时变量,所以先是:

    B delete
    A delete
    
    • 1
    • 2

    再是c变量自己:

    C delete
    B delete
    A delete
    
    • 1
    • 2
    • 3

    再是 c变量里面的类成员 b:

    B delete
    A delete
    
    • 1
    • 2

    最后是主程序中的 B b1变量的析构调用:

    B delete
    A delete
    
    • 1
    • 2

    由此析构函数执行完毕。

    默认构造函数带缺省值

    假设在B类中,有:

    B(int v=1):p(v){cout<<"B constructed"<<endl;}
    
    • 1

    那么当主程序中创建变量时:

    B b1;
    
    • 1

    将会执行上面那个构造函数,所以b1.p=1(为了方便,这里对p使用了公开)。

    如果同时写:

    B(){cout<<"B constructed"<<endl;}
    B(int v=1):p(v){cout<<"B 缺省constructed"<<endl;}
    
    • 1
    • 2

    这样的话,编译是错误的,因为两个构造函数,导致编译器不知道调用哪个。

  • 相关阅读:
    【SpringCloud微服务实战02】Ribbon 负载均衡
    PAT 1004 Counting Leaves (C++)
    报销流程|By天放师兄
    刷题之路:1410 - 【基础】数塔的行走路径(递推求解)c++题解
    自动化办公03 用xlrd和xlwt库操作excel.xls文件(老版本)
    【jvm】虚拟机栈之局部变量表
    性能测试持续集成 CICD:JMeter+Jenkins+Ant+jmx
    Matplotlib光速入门-从安装到常用实战
    去除upload的抖动效果
    TCP发送接口(如send(),write()等)的返回值与成功发送到接收端的数据量无直接关系
  • 原文地址:https://blog.csdn.net/qq_41286751/article/details/133758235