周六参加了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的构造函数。所以最后可以得到结论:先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。
结论
在类被构造的时候,先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。析构函数与之完全相反,互成镜像。