目录:
定义
使用初始化列表的原因
必须使用初始化列表的时候
成员变量的顺序
定义
与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
从概念上来讲,构造函数的执行可以分成两个阶段:初始化阶段和计算阶段,初始化阶段先于计算阶段。
初始化阶段
所有类类型(class Type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。
计算阶段
一般用于执行构造函数体内的赋值操作。
下面的代码中,其中 Date 有构造函数,拷贝构造函数及赋值运算符,为的是方便查看结果。Time 是个测试类,它以 Date 的对象为成员,我们看一下 Time 的构造函数是怎么样执行的。
class Date
{
public:
Date() // 无参构造函数
{
cout << "Date()" << endl;
}
Date(const Date& d) // 拷贝构造函数
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
}
Date& operator=(const Date& d) // 赋值运算符
{
if (this != &d)
{
cout << "Date& operator=(const Date& d)" << endl;
_year = d._year;
return *this;
}
}
private:
int _year;
};
class Time
{
public:
Time(Date& d)
{
_d = d;
}
private:
Date _d;
};
测试用例:
void Test()
{
Date d1;
Time t1(d1);
}
输出结果:
解释说明:
第一行输出对应测试用例中的第一行,构造一个Date对象
第二行输出对应Time构造函数中的代码,此时会先调用Date类的默认构造函数初始化对象 _d,即所谓的初始化阶段。
第三行输出对应Time的赋值运算符,对 _d 执行赋值操作,即所谓的计算阶段。
使用初始化列表的原因
初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
主要是性能问题,对于内置类型,如int, double等,使用初始化列表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由下面的测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。同样看上面的例子,我们使用初始化列表来实现Time的构造函数。
class Time
{
public:
Time(Date& d)
: _d(d)
{}
private:
Date _d;
};
使用同样的测试用例,输出结果如下:
解释说明:
第一行输出对应测试用例中的第一行,构造一个Date对象。
第二行输出对应Time的初始化列表,直接调用拷贝构造函数初始化 _d,省去了调用默认构造函数的过程。
所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。
必须使用初始化列表的时候
除了性能问题之外,有些时候初始化列表是不可或缺的,以下几种情况时必须使用初始化列表
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
class Date
{
public:
Date(int year)
: _year(year)
{}
private:
int _year;
};
class Time
{
public:
Time(Date& d)
{
_d = d;
}
private:
Date _d;
};
以上代码无法通过编译,因为Time的构造函数中 _d = d这一行实际上分成两步执行:
首先调用Date的默认构造函数来初始化_d
由于Date没有默认的构造函数,所以无法执行初始化_d,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作
class Time
{
public:
Time(Date& d)
: _d(d)
{}
private:
Date _d;
};
成员变量的顺序
成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的,如下代码:
class Fun
{
public:
//先初始化_x,再初始化_y
Fun(int tmp)
: _x(tmp)
, _y(_x)
{}
private:
int _x;
int _y;
};
再看下面的代码:
class Fun
{
public:
//_y未定义
Fun(int tmp)
: _y(tmp)
, _x(_y)
{}
private:
int _x;
int _y;
};
这里 _x 的值是未定义的,虽然 _y 在初始化列表里面出现在 _x 前面,但是 _x 先于 _y 定义,所以必须先初始化 _x,而 _x 由 _y 初始化,此时 _y 尚未初始化,所以导致 _x 的值未定义。一个好的习惯是,按照成员定义的顺序进行初始化 。