在类和对象之构造函数与析构函数中,我们可以使用构造函数来完成初始化,在创建变量时,编译器会通过调用构造函数,给对象中各个成员变量一个合适的初始值。
但这真的叫做初始化吗?
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。
普通的构造函数并不是真正的初始化方式,真正的初始化方式应该是用初始化列表
初始化列表的形式是:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟 一个放在括号中的初始值或表达式。
class A
{
public:
A(int a,int b)
:_a(a)
,_b(b)
{
//函数体
}
private:
int _a;
int _b;
};
初始化列表完成初始化,但好像不用初始化列表,仅依靠构造函数似乎也可以做到这样的事,那为什么要引进初始化列表?
还是拿日期类举个例子,假如说初始化的时候用了个非法的日期初始化,构造函数很难在赋初值的同时帮我们检查错误,仅靠构造函数是无法在第一时间检查出错误。
而用初始化列表来初始化就可以很轻松检查出错误
//构造函数
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
//初始化列表
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
if (month < 1 || month>12 || day<1 || day>Days(year, month))
{
perror("illegal date");
assert(false);
}
}
int Days(int y, int m)
{
int arr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (m == 2 )
if ( y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
return 29;
return arr[m];
}
private:
int _year;
int _month;
int _day;
};
这样,就算是非法日期,初始化列表也能很快发现错误。
并且,初始化列表能做到的事还远不止这些。
假设类里面有一个const修饰的变量需要初始化,凭借构造函数仍然不行,编译器的报错也不难理解:构造函数的本质是赋初值,既然是赋值,那它凭什么可以随意修改const修饰的变量呢
class A
{
public:
A(int a, int b,int c)
:_a(a)
,_b(b)
,_c(c)
{}
private:
int _a;
int _b;
const int _c;
};
类似地,类里面声明的引用成员变量也只能用初始化列表初始化。
所以,总结起来就是类中包含以下成员,必须放在初始化列表位置进行初始化:
1.引用成员变量2. const成员变量 3.自定义类型成员(且该类没有默认构造函数时)4.希望对初始化进行检查
接着,在来看一个问题,并思考程序运行后将输出什么
class A
{
public:
A(int a)
:_a(a)
, _b(_a)
{}
void Print() {
cout << _a << " " << _b << endl;
}
private:
int _b;
int _a;
};
int main() {
A tmp(1);
tmp.Print();
}
A. 输出1 1 B.程序崩溃 C.编译不通过 D.输出1 随机值
想必这里将1传入A的初始化列表中后_a将被初始化成a的值,也就i是1,_b也被_a初始化成1.
可是,当我们到编译器上运行后结果却是这样:
之所以会输出这样的结果,是因为成员变量初始化的顺序其实与其在初始化列表中的先后次序无关,而应该取决于其在初始化列表中的初始化顺序。
_b比_a更先声明,理应是_b优先初始化,_b拿一个未初始化的_a来初始化,也就会是随机值。