类的初始化通产有3种类型:使用初始化列表,在构造函数体中赋值,以及使用默认构造函数。
先说前两种:
初始化列表与在构造函数体中赋值的区别在哪里呢?主要有两点:
第一,有的成员不能使用函数体中的“=”初始化。这其实就是初始化与赋值的区别:比如比如const 成员,引用类型,以及没有定义默认构造函数的类,它们都必须在定义时初始化,而不能赋值,这时候我们的初始化列表就派上用场了。
第二,初始化的顺序不同。初始化的顺序看起来似乎无关紧要,但是如果一个数据的初始化依赖于另一个,就很重要。举一个不恰当的例子:
//构造函数
//初始化列表中先试图先初始化ival1,再用ival1初始化ival2
NoName(int i1):ival1(i1),ival2(ival1){}
string *pstring;
//先定义ival2,在定义ival1
int ival2;
int ival1;
如果调用NoName nm2(2);结果是ival1 = 2,ival2;的值不确定。如果调用NoName nm2(2);结果是ival1 = 2,ival2;的值不确定。如果调用NoName nm2(2);结果是ival1 = 2,ival2;的值不确定。这是因为初始化的顺序不是初始化列表的顺序,而是定义类成员的顺序!所以实际上,会用不确定的值初始化ival2,然后再用2初始化ival1.
而使用函数体初始化就避免了这个问题,哪个语句在前,先赋值初始化谁:
NoName(int i1)
{
ival1 = i1;
ival2 = ival1;
}
这样两个成员都被初始化为2了。这样两个成员都被初始化为2了。这样两个成员都被初始化为2了。
说完了前两种,再看看默认构造函数:
如果定义一个类时没有提供初始化式,则使用默认构造函数。但如果这个类哪怕定义了一个构造函数(即使它缺陷很多),系统也不会使用默认的构造函数。
如果没有这个类定义构造函数,则会为它合成一个默认的构造函数,合成规则与变量的初始化规则相同:对于类成员,通过运行它的默认构造函数来初始化;对于内置或者符合类型的成员,因为编译器应该只对全局的这种变量初始化,而类的成员不是全局的,所以这样就会初始化失败。
通常情况下,应该为一个类定义一个默认构造函数,这样可以省去定义对象时的许多麻烦,尤其是遇到类的成员是另一个类类型时,如果这个类类型没有定义默认构造函数,那么定义这个类的构造函数时,都要为这个类的那个类类型的成员的构造函数传递参数。