1.有三种情况下是一定要用初始化列表的,单类的数据成员里面有const成员,或者有数据成员是引用,或者有没有默认构造函数的类成员。const和引用成员可以被初始化,但不可以被赋值。而如果一个类成员没有用初始化列表初始化的时候,编译器在进入构造函数的第一条语句前会先调用类成员的默认构造函数。而如果该类数据成员没有默认构造函数的时候,就会出项错误。这三种情况下是一定要用初始化列表的。
2.在构造函数初始化列表里面没有显式提及的类数据成员按如下规则初时化:如果是内置类型则根据该对象的位置而定,如果对象是全局,那么内置类型被初始化为零,如果不是全局,那么内置类型未知。如果是类类型则调用该类的默认构造函数。所以如果那个类没有默认构造函数,而又不在初始化列表中显式提及,就会出错!
在前面的例程中,我们对成员数据的初始化,都是在函数体中进行的,但有些情况下这种初始化的方法是行不通的,例如:
#include <iostream>
using namespace std;
class Date{
int da, mo;
const int yr;//const常量
public:
Date(int d, int m, int y) //有参数的构造函数
{
cout << "Have parameter: ";
da = d;
mo = m;
yr = y; //此处有错误
}
void display()
{
cout << "\n" << mo << "-" << da << "-" << yr;
}
};
int main()
{
Date a(17,4,2007);
a.display();
getchar();
return 0;
}
在类Data中有一个成员yr是一个const int类型,它是不能在函数体中被重新赋值的。这种情况下我们只有使用另一种特殊的初始化方式——初始化列表。初始化列表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
构造函数初始化列表的使用规则:
如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
class A {
…
A(int x); // A的构造函数
};
class B : public A{
…
B(int x, int y);// B的构造函数
};
B::B(int x, int y): A(x){ // 在初始化表里调用A的构造函数
…
}
类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。
非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。
class A{
…
A(void); // 无参数构造函数
A(const A &other); // 拷贝构造函数
A & operate =( const A &other); // 赋值函数
};
class B{
public:
B(const A &a); // B的构造函数
private:
A m_a; // 成员对象
};
示例(a)中,类B的构造函数在其初始化表里调用了类A的拷贝构造函数,从而将成员对象m_a初始化。
示例(b)中,类B的构造函数在函数体内用赋值的方式将成员对象m_a初始化。我们看到的只是一条赋值语句,但实际上B的构造函数干了两件事:先暗地里创建m_a对象(调用了A的无参数构造函数),再调用类A的赋值函数,将参数a赋给m_a。
B::B(const A &a): m_a(a){ //示例(a) 成员对象在初始化表中被初始化
…
}
B::B(const A &a){ //示例(b) 成员对象在函数体内被初始化
m_a = a;
…
}
对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些。若类F的声明如下:
class F{
public:
F(int x, int y); // 构造函数
private:
int m_x, m_y;
int m_i, m_j;
}
示例(c)中F的构造函数采用了第一种初始化方式,示例(d)中F的构造函数采用了第二种初始化方式。
F::F(int x, int y): m_x(x), m_y(y){ //示例(c)数据成员在初始化表中被初始化
m_i = 0;
m_j = 0;
}
F::F(int x, int y){ //示例(d)数据成员在函数体内被初始化
m_x = x;
m_y = y;
m_i = 0;
m_j = 0;
} (引自<<高质量c++编程指南>>)