周六参加了XX的笔试,挺崩的,很多继承,多重继承等等方面的问题,记录下。

析构函数与构造函数的顺序

继承

先看看下面的代码

#include <iostream>
#include <cmath>
using namespace std;

class A
{
public:
	A(){cout << "Construct A" << endl;}
	~A(){cout << "Destruct A" << endl;}
};

class C
{
public:
	C(){cout << "Construct C" << endl;}
	~C(){cout << "Destruct C" << endl;}
	
};

class B: public A, public C
{
public:
	B(){cout << "Construct B" << endl;}
	~B(){cout << "Destruct B" << endl;}
};

int main(int argc, char const *argv[])
{
	B b;
	return 0;
}
类图
类图

这里有三个类,其中A,C类是B的父类,然后在Main函数中声明一个B类型的变量,然后程序的输出是这样的:

	Construct A
	Construct C
	Construct B
	Destruct B
	Destruct C
	Destruct A

这里涉及到构造以及析构的顺序,首先看看构造。构造函数执行的顺序是,先构造父类,再构造子类,其中父类的构造顺序是从左到右。然后析构函数执行的顺序是跟构造函数正好相反的,先执行自身的析构函数,然后再依次从右到左执行父类的析构函数。

上面说的是不涉及列表初始化,以及虚拟继承这些的。那接下来看看加上列表初始化会怎么样。

列表初始化成员变量

现在ABC之间不再是继承关系,而是组合关系。B类中有A和C两个类型的变量。

	#include <iostream>
	#include <cmath>
	using namespace std;

	class A
	{
	public:
		A(){cout << "Construct A" << endl;}
		~A(){cout << "Destruct A" << endl;}
	};

	class C
	{
	public:
		C(){cout << "Construct C" << endl;}
		~C(){cout << "Destruct C" << endl;}
		
	};

	class B
	{
	public:
		// Notice
		B(): a(A()), c(C()) {cout << "Construct B" << endl;}
		~B(){cout << "Destruct B" << endl;}
		C c;
		A a;
	};

	int main(int argc, char const *argv[])
	{
		B b;
		return 0;
	}
类图
类图

输出是这样的~

	Construct C
	Construct A
	Construct B
	Destruct B
	Destruct A
	Destruct C

可以看出,列表初始化是先于构造函数的调用的,而且列表初始化是与初始化顺序无关,只与数据成员定义的顺序有关。在上面的例子中C类型的变量定义在A类型的变量前面,因此会先构造C,之后构造A。

继承与列表初始化

下面的例子中B类继承了A和C,然后又拥有一个A和C类型的成员变量,虽然不符合设计模式,但是就将就看了。

#include <iostream>
	#include <cmath>
	using namespace std;

	class A
	{
	public:
		A(){cout << "Construct A" << endl;}
		~A(){cout << "Destruct A" << endl;}
	};

	class C
	{
	public:
		C(){cout << "Construct C" << endl;}
		~C(){cout << "Destruct C" << endl;}
		
	};

	class B: public A, public C
	{
	public:
		//Notice: List initialize
		B(): a(A()), c(C()) {cout << "Construct B" << endl;}
		~B(){cout << "Destruct B" << endl;}
		C c;
		A a;
	};

	int main(int argc, char const *argv[])
	{
		B b;
		return 0;
	}

在这样的例子中输出是这样的~

	Construct A
	Construct C
	Construct C
	Construct A
	Construct B
	Destruct B
	Destruct A
	Destruct C
	Destruct C
	Destruct A

也就是说,类在构造的时候会先从左到右调用父类的构造函数,然后根据类中数据成员的定义依次构造/*注意:是与列表初始化顺序无关*/,然后才会调用自身的构造函数,而析构函数则正好相反。

虚拟继承,继承,与列表初始化

虚拟继承是什么,暂时还不知道。但是可以看看虚拟继承的构造析构顺序是如何的。

#include <iostream>
#include <cmath>
using namespace std;

class A
{
public:
	A(){cout << "Construct A" << endl;}
	~A(){cout << "Destruct A" << endl;}
};

class C
{
public:
	C(){cout << "Construct C" << endl;}
	~C(){cout << "Destruct C" << endl;}
	
};

//Notice: C is a virtual public
class B: public A, public virtual C
{
public:
	B(): a(A()), c(C()) {cout << "Construct B" << endl;}
	~B(){cout << "Destruct B" << endl;}
	C c;
	A a;
};

int main(int argc, char const *argv[])
{
	B b;
	return 0;
}

继承跟虚拟继承初始化的顺序是不一样的,在这个例子中,输出顺序是这样的:

	Construct C
	Construct A
	Construct C
	Construct A
	Construct B
	Destruct B
	Destruct A
	Destruct C
	Destruct A
	Destruct C

跟之前相比,父类的构造顺序发生了改变,C的构造函数先被执行,然后是AC的构造函数。所以最后可以得到结论:先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。

结论

在类被构造的时候,先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。析构函数与之完全相反,互成镜像。

评论