• 【C++】构造函数初始化列表 ④ ( 构造函数 和 析构函数 调用顺序分析 )



    构造函数初始化列表 总结 :

    • 初始化列表 可以 为 类的 成员变量 提供初始值 ;
    • 初始化列表 可以 调用 类的 成员变量 类型的 构造函数 进行成员变量初始化操作 ;
    • 初始化列表 可以 使用 构造函数 中传入的 参数 ;
    • 类初始化时 , 根据定义顺序 , 先调用 成员变量的 构造函数 , 然后调用外部类构造函数 , 析构函数正好相反 ;
    • 实例对象 的 const 成员变量 必须只能在 初始化列表 中进行 初始化 , 所有的构造函数都要进行初始化操作 ;




    一、构造函数 和 析构函数 调用顺序 说明




    1、构造函数调用顺序


    在一个类 C 中 , 嵌套了 A 和 B 两个类类型的 对象 作为 成员变量 ;


    构造函数的 调用顺序如下 :

    • 先调用 被嵌套类 A 和 B 的构造函数 , 再调用外部 C 类的构造函数 ;
    • A 和 B 构造函数 , 成员变量 中 谁先声明 , 就先调用谁的 构造函数 ;
      • 注意 : A 和 B 在 构造函数 初始化列表 中的顺序 , 与先调用谁的构造函数无关 ;

    2、析构函数调用顺序


    析构函数调用顺序 与 构造函数调用顺序相反 , 直接 将 构造函数 调用顺序 倒序排列即可 ;


    3、拷贝构造函数也可以定义初始化列表


    如果一个类 没有定义 无参构造函数 , 只有一个 有参的构造函数 ,

    此时 , C++ 编译器 不会为其 生成 默认的无参构造函数 ;


    这种场景下 涉及到了 构造函数 的类型 :

    • 强制在初始化列表中调用构造函数 : 如果类中定义了 有参构造函数 , 导致 无参构造函数 被屏蔽 , 那么 在 所有的构造函数的 初始化列表中 , 都必须强制调用 子对象 的 构造函数 ;
    • 不强制在初始化列表中调用构造函数 : 如果类中定义了 无参构造函数 , 或者 有默认的 无参构造函数 , 那么在 初始化列表 中不强制调用 子对象 的构造函数 ;

    使用如下方式 , 声明 A 和 B 类型的成员变量 , 会自动调用 默认的无参构造函数 初始化对象 , 但是由于 A 和 B 中定义了 有参构造函数 , 无参构造函数 被屏蔽了 ;

    	A m_a;			// A 类型成员变量
    	B m_b;			// B 类型成员变量
    
    • 1
    • 2

    没有 无参构造函数 , 上面声明的 A 和 B 两个对象便无法创建成功 ;

    此时 , 只能在 构造函数的 初始化列表 中 , 调用 A 和 B 的 有参构造函数 创建 A B 两个成员变量 ;


    拷贝构造函数 也是 构造函数 , 也必须在 初始化列表 中 调用 构造函数 , 对子对象进行初始化操作 ;





    二、构造函数 和 析构函数 调用顺序 代码分析




    1、构造函数调用顺序


    在下面的代码中 ,

    定义了 类 A , 该类实现了 有参构造函数 , 其 无参构造函数 被屏蔽 , 如果要初始化 A 类型的对象 , 必须使用有参构造函数 , 使用 A a 的形式定义的变量 , 无法进行初始化 ;

    class A
    {
    public:
    	// 带参构造函数
    	A(int age, int height)
    	{
    		m_age = age;
    		m_height = height;
    		cout << "执行 A 的构造函数" << endl;
    	}
    
    	~A()
    	{
    		cout << "执行 A 的析构函数" << endl;
    	}
    
    public:
    	int m_age;		// 年龄
    	int m_height;	// 身高
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    定义了 类 B 与 上述 类 A 基本一致 , 也是无法使用 默认的无参构造函数 , 必须调用有参构造函数 ;


    定义 类 C , 其中维护了 A 和 B 两个子对象 ,

    public:
    	int m_age;		// 年龄
    	A m_a;			// A 类型成员变量
    	B m_b;			// B 类型成员变量
    	const int m_const_int;	// 常量成员
    
    • 1
    • 2
    • 3
    • 4
    • 5

    由于 A 和 B 都无法使用 无参构造函数 , 因此在 类 C 的所有构造函数 ( 包括 拷贝构造函数 ) 的 初始化列表中 , 必须强制调用 A 和 B 的 有参构造函数 ;

    • 此外由于 还定义了 const int m_const_int 常量成员 , 类 C 的 所有构造函数 ( 包括 拷贝构造函数 ) 的 初始化列表中 , 同时也必须强制对 常量成员进行初始化 ;
    	C() : m_age(10), m_b(5, 110), m_a(10, 150), m_const_int(888)
    	{}
    
    • 1
    • 2

    最终的 构造函数 执行顺序是 :

    执行 A 的构造函数
    执行 B 的构造函数
    执行 C 的构造函数
    执行 A 的构造函数
    执行 B 的构造函数
    执行 C 的 拷贝构造函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行

    	// 通过 C 的有参构造函数
    	// 其中 构造函数中的参数 作为 参数列表 中的参数值
    	C c(10, 10, 150, 18, 180);
    
    • 1
    • 2
    • 3

    代码时 , 先后执行 A -> B -> C 类的构造函数 ;

    执行

    	// 调用 C 的拷贝构造函数
    	C c2 = c;
    
    • 1
    • 2

    代码时 , 先执行 A -> B 的构造函数 , 然后执行 C 的拷贝构造函数 ;


    2、代码示例 - 构造 / 析构 函数调用顺序分析


    #include "iostream"
    using namespace std;
    
    class A
    {
    public:
    	// 带参构造函数
    	A(int age, int height)
    	{
    		m_age = age;
    		m_height = height;
    		cout << "执行 A 的构造函数" << endl;
    	}
    
    	~A()
    	{
    		cout << "执行 A 的析构函数" << endl;
    	}
    
    public:
    	int m_age;		// 年龄
    	int m_height;	// 身高
    };
    
    class B
    {
    public:
    	// 带参构造函数
    	B(int age, int height)
    	{
    		m_age = age;
    		m_height = height;
    		cout << "执行 B 的构造函数" << endl;
    	}
    
    	~B()
    	{
    		cout << "执行 B 的析构函数" << endl;
    	}
    
    public:
    	int m_age;		// 年龄
    	int m_height;	// 身高
    };
    
    class C
    {
    public:
    	C() : m_age(10), m_b(5, 110), m_a(10, 150), m_const_int(888)
    	{}
    
    	// 构造函数中的参数可以作为 参数列表 中的参数值
    	C(int age, int ageOfA, int heightOfA, int ageOfB, int heightOfB) 
    		: m_age(age),m_b(ageOfB, heightOfB), m_a(ageOfA, heightOfA), m_const_int(888)
    	{
    		cout << "执行 C 的构造函数" << endl;
    	}
    
    	C(const C& c) : m_b(5, 110), m_a(10, 150), m_const_int(888)
    	{
    		// 上面的 3 个变量 , 必须在初始化列表中初始化 
    		// m_age 可以在后续进行单独赋值 , 可以不在初始化列表中进行初始化
    		// 由于 A 和 B 都没有默认构造函数 , 必须在初始化列表中调用 有参构造函数
    		// m_const_int 成员是常量 , 只能初始化一次 , 不能赋值 , 
    		//	因此也必须在初始化列表中进行初始化
    		cout << "执行 C 的 拷贝构造函数" << endl;
    	}
    
    	~C()
    	{
    		cout << "执行 C 的析构函数" << endl;
    	}
    public:
    	int m_age;		// 年龄
    	A m_a;			// A 类型成员变量
    	B m_b;			// B 类型成员变量
    	const int m_const_int;	// 常量成员
    };
    
    
    int main()
    {
    	// 通过 C 的有参构造函数
    	// 其中 构造函数中的参数 作为 参数列表 中的参数值
    	C c(10, 10, 150, 18, 180);
    
    	// 调用 C 的拷贝构造函数
    	C c2 = c;
    
    
    	// 控制台暂停 , 按任意键继续向后执行
    	system("pause");
    	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
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    执行结果 :

    执行 A 的构造函数
    执行 B 的构造函数
    执行 C 的构造函数
    执行 A 的构造函数
    执行 B 的构造函数
    执行 C 的 拷贝构造函数
    Press any key to continue . . .
    执行 C 的析构函数
    执行 B 的析构函数
    执行 A 的析构函数
    执行 C 的析构函数
    执行 B 的析构函数
    执行 A 的析构函数
    
    D:\002_Project\006_Visual_Studio\HelloWorld\HelloWorld\Debug\HelloWorld.exe (进程 19692)已退出,代码为 0。
    要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
    按任意键关闭此窗口. . .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

  • 相关阅读:
    力扣刷题-链表-设计链表
    HTML所有功能大汇总
    AVL树中旋转算法的调用
    Java 版 spring cloud 工程系统管理 +二次开发 工程项目管理系统源码
    R语言cochran_armitage检验
    Docker的数据管理和端口映射实现容器访问
    泛型实例运用
    电商技术揭秘三十一:智能风控与反欺诈技术
    无TMP走Dev通道升级win11后续更新方法
    【全志T113-S3_100ask】2-编写第一个驱动
  • 原文地址:https://blog.csdn.net/han1202012/article/details/133025809