在一些情况下,初始化列表可以做到构造函数做不到的事情:
1. 类里面有const类型的成员,它是不能被赋值的,所以需要在初始化列表里面初始化它;
2. 引用类型的成员(也就是名字成员,它作为一个现有名字的别名),也是需要在初始化列表里面初始化的,目的是为了生成了一个其名字成员在类外可以被修改而在内部是只读的对象;
3. 需要调用基类的构造函数,且此基类构造函数是有参数的;
4. 类里面有其他类类型的成员,且这个“其他类”的构造函数是有参数的。
举个例子:
Class Old
{
public:
Old(int &);
private:
int o1;
char *o2;
}
Class New
{
public:
New();
Old oldman;
int n1;
}
New::New(): oldman(100, "long") /* 如果类的构造函数是有参数的,那么编译器就不会默认生成oldman的构造函数,而显示的初始化oldman是必须的,因此只有通过初始化列表来为oldman传递参数 */
{
n1 = 150;
oldman = ...; /* 在构造函数里面加oldman的赋值语句就也没有问题了 */
}
情况3和4其实一样的道理。
再来考虑一下效率问题,还是举个String类有关的例子吧。
Class Text
{
public:
Text();
private:
String word;
int line;
};
如果用构造函数来初始化word和line,都需要做哪些事情呢?
Text::Text()
{
word = 0;
line = 0;
}
在这里,其实编译器做了很多事情,
首先,创建一个String类型的临时对象,
然后,对这个临时对象进行初始化,
然后,把临时对象赋值给word,
然后,销毁临时对象。
(line的就不说了)
那么如果用初始化列表来初始化word呢?
Text::Text():word(0)
{
line = 0;
}
它会被扩展成
Text::Text()
{
word.String(0);
line = 0;
}
是不是开销要小些了?!
最后还得说一下初始化列表的执行顺序,初始化的执行顺序并非初始化列表的赋值顺序,而是按照类里面变量的声明顺序。举个例子:
Class A
{
public:
A();
private:
int a;
char b;
double c;
double d;
};
Class A::A():b('x'), a(1), d(0.1), c(0.2)
{}
初始化列表的顺序是b、a、d、c,而实际的赋值执行顺序其实是a、b、c、d。
注意这个干什么用呢?看下例,
Class A::A():b('x'), a(1), d(0.1), c(d)
{}
呵呵,看出问题了吧:)