• 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

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

  • 相关阅读:
    水库大坝可视化智能远程监管方案,助力安全监测智能巡检
    pandas|Task03索引
    C#WPF数字大屏项目实战06--报警信息
    设计模式-行为型设计模式-命令模式
    基于边缘物联网关的智慧零售应用方案
    19-Mitt
    Mac苹果电脑分辨率修改管理 安装SwitchResX 完美解决
    常用sql语句
    SRP:单一职责原则
    1500*B. Zero Array(贪心&数学&找规律)
  • 原文地址:https://blog.csdn.net/qq_41286751/article/details/133758235