构造函数的执行过程会分成两个阶段:
1 初始化阶段(如果是成员变量,类成员的声明顺序,如果是继承的类 ,根据继承顺序 ) 2 赋值阶段 (构造函数中代码顺序)
旁白:变量或者类在 定义是,分配空间后,第一次 赋予初值即为初始化,
默认初始化和显示初始化之分,
C++何时要使用成员初始化列表? 初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
一,需要初始化类成员
首先把数据成员按类型分类并分情况说明:
二,需要初始化const数据成员;
三,需要初始化引用数据成员;
对于第一种情况,大家知道,对于类对象,初始化和赋值是不同的,赋值会带来了很大的性能开销(类的关系-组合) 对于第二种以及第三种情况,const 成员或引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。(初始化只有一次)
先看看下面的代码
#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 //自己
可以看出,
列表初始化是先于构造函数的调用的,而且列表初始化是与初始化顺序无关,只与数据成员定义的顺序有关。
在上面的例子中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 //virutal继承优先
Construct A//普通继承其次
Construct C //成员构造
Construct A //成员构造
Construct B //自己
跟之前相比,父类的构造顺序发生了改变,C的构造函数先被执行,然后是AC的构造函数。所以最后可以得到结论:先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。
在类被构造的时候(有继承和组合),
旁白:类与类关系 分为2中继承和组合
参考
1 http://gaocegege.com/Blog/cpp/cppclass
2 http://www.runoob.com/w3cnote/cpp-construct-function-initial-list.html