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

  • 初始化列表 可以 为 类的 成员变量 提供初始值 ;
  • 初始化列表 可以 调用 类的 成员变量 类型的 构造函数 进行成员变量初始化操作 ;
  • 初始化列表 可以 使用 构造函数 中传入的 参数 ;
  • 类初始化时 , 根据定义顺序 , 先调用 成员变量的 构造函数 , 然后调用外部类构造函数 , 析构函数正好相反 ;
  • 实例对象 的 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 类型成员变量

没有 无参构造函数 , 上面声明的 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;	// 身高
};

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


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

public:
	int m_age;		// 年龄
	A m_a;			// A 类型成员变量
	B m_b;			// B 类型成员变量
	const int m_const_int;	// 常量成员

由于 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)
	{}

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

执行 A 的构造函数
执行 B 的构造函数
执行 C 的构造函数
执行 A 的构造函数
执行 B 的构造函数
执行 C 的 拷贝构造函数

执行

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

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

执行

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

代码时 , 先执行 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;
}

执行结果 :

执行 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。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

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

09-20 12:30