c++类构造函数的初始化列表是一个挺有灵性的东西,值得花一些时间去研究下。
1.分配内存,调用构造函数时,隐式或显式的初始化各数据成员;
2.进入构造函数后在构造函数中执行一般赋值与计算。
对于类的构造函数,有如下两种形式:
第一种是不使用初始化列表的:
class B
{
public:
B(const A & item){
_a = item;
};
~B(){};
private:
A _a;
};
第二种是使用初始化列表的,形如:
class B
{
public:
B(const A & item):_a(item){
};
~B(){};
private:
A _a;
};
我们这里之所以使用一个自定义的类A作为类B的成员,是想通过自定义的复制控制函数来看看究竟以上二者间到底有什么区别。完整的代码如下:
#include
using std::cout;
using std::endl;
class A
{
public:
A() {
cout << "A()" << endl;
}
A(const A & other) {
cout << "A(const A & other)" << endl;
}
A & operator=(const A & other) {
cout << "operator=(const A & other)" << endl;
return *this;
}
~A() {
cout << "~A()" << endl;
}
};
class B
{
public:
B(const A & item):_a(item){
// _a = item;
};
~B(){};
private:
A _a;
};
int main()
{
A a;
B b(a);
system("pause");
return 0;
}
我们测试后发现输出如下:
B | 输出 |
---|---|
非参数列表 | A() A() operator=(const A & other) |
参数列表 | A() A(const A & other) |
由此可见,在第一种形式下,B类对象的构造过程实际上是先调用了A类的默认构造函数,然后再调用了赋值函数完成了B类的A对象成员的构造;而在第二种形式下,直接是调用了复制构造函数一步完成的。
根据上面的分析,我们就不难理解为什么说一些特定场景下必须要使用初始化列表了,总结起来就是:
如果类的成员是const、引用、或者是未提供默认构造函数的类,我们就必须通过构造函数初始化列表为这些成员提供初值
例如:
class B
{
public:
B(const A & item, int i){
_a = item; //ok or error
_i = i; //ok
_ii = i; //error
_ri = i; //error
};
~B(){};
private:
A _a;
int _i;
const int _ii;
int &_ri;
};
根据《c++ primer》 给出的建议:
由于在很多类中,初始化和赋值的区别事关底层效率的问题:我们举个例子看看链表吧,做一次赋值操作很有可能要先做delete之后再做一次复制构造的全过程,代价是巨大的,因此应该养成使用初始化列表的习惯。
使用初始化列表的时候需要注意的是,成员的初始化顺序与他们在类定义中出现的顺序一致,最好尽量避免使用类的成员去初始化其他成员,这样我们就可以不用关心初始化的顺序了。