初始化列表
定义
构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化列表)。初始化列表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
Date(int year, int month, int day)//带参构造函数
:_year(year)
, _month(month)
, _day(day)
{}
构造函数的两个执行阶段
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
初始化阶段:
所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。
计算阶段
一般用于执行构造函数体内的赋值操作。
为什么使用初始化列表
使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?我们看例子:
使用构造函数函数体内赋值:
class Test1
{
public:
Test1()//无参的构造函数
{
cout << "Construct Test1" << endl;
}
Test1(const Test1& t1) // 拷贝构造函数
{
cout << "Copy constructor for Test1" << endl;
}
Test1& operator=(const Test1& t1)
{
cout << "operator=()" << endl;
a = t1.a;
return *this;
}
protected:
int a;
};
class Test2
{
public:
Test1 test1;
Test2(Test1 &t1)
{
test1 = t1;
}
};
int main()
{
Test1 t1;
Test2 t2(t1);
system("pause");
return 0;
}
输出结果:
使用初始化列表赋值:
class Test1
{
public:
Test1()//无参的构造函数
{
cout << "Construct Test1" << endl;
}
Test1(const Test1& t1) // 拷贝构造函数
{
cout << "Copy constructor for Test1" << endl;
}
Test1& operator=(const Test1& t1)
{
cout << "operator=()" << endl;
a = t1.a;
return *this;
}
protected:
int a;
};
class Test2
{
public:
Test1 test1;
Test2(Test1 &t1)
:test1(t1)
{}
};
int main()
{
Test1 t1;
Test2 t2(t1);
system("pause");
return 0;
}
输出结果:
从上面的两个代码比较可以看出使用初始化列表,直接调用拷贝构造函数初始化,省去了默认构造函数的过程。所以能使用初始化列表的时候尽量使用初始化列表。
哪些东西必须放在初始化列表中
- 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
成员变量的初始化顺序
成员是按照他们在类中声明的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的。例如:
class Date
{
public:
Date(int year, int month, int day)//带参构造函数
:_month(month)//第二个初始化
, _year(year) //第一个初始化
, _day(day) //第三个初始化
{}
private:
int _year; //第一个定义
int _month;//第二个定义
int _day; //第三个定义
};