对于构造函数我们一直有个误区,就是构造函数用于初始化成员变量创建对象,实际上并不是这样的。真正的初始化并不是在构造函数中完成的,而是在初始化列表中完成的,构造函数中的实际上是赋值操作。
要了解构造函数的工作原理,得先了解什么是初始化列表
初始化列表: 以一个冒号开始,接着是一个以逗号分隔的成员变量列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。初始化列表才是成员变量初始化定义的地方,也就是为其开辟空间的地方。
具体我们来看下面代码,我们有一个日期类对象,我们可以这样来定义构造函数:
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
protected:
int _year;
int _month;
int _day;
};
也可以利用初始化列表:
class Date
{
public:
//使用初始话列表初始化成员变量
Date(int year,int month,int day):_year(year), _month(month), _day(day){}
protected:
int _year;
int _month;
int _day;
};
了解了什么是初始化列表之后,继续学习初始化列表的特性,但是学完后你就会发现初始化列表的特性其实就是构造函数的工作原理。
初始化列表的特性/构造函数的工作原理:
-
所有构造函数都要先走初始化列表再走函数体,初始化列表会给每个成员都进行一次初始化,如果你的初始化列表显示初始化了某个成员,它就会去初始化,如果你没有显式初始化某个成员,它也会初始化,对于基本类型的成员变量,它会按照默认值进行初始化,如果没有设置默认值,就会用随机值进行初始化,对于自定义类型成员会调用它的默认构造函数进行初始化,如果该自定义类型的成员变量没有默认构造函数,则编译器调用构造函数创建对象的时候就会报错,因为当你没有在构造函数的初始化化列表中对自定义类型成员进行显示初始化的时候,编译器会调用自定义类型成员的默认构造函数在初始化列表中来对其进行初始化,而自定义类型的成员变量没有默认构造函数,所以会报错,所以我们需要在初始化列表中对没有默认构造函数的自定义类型成员变量进行显式初始化,即显示调用该自定义类型成员变量的其它构造函数。
也就是说尽管你在构造函数体内进行“初始化”操作,实际上人家已经在初始化列表中就初始化过了,你在构造函数体内的“初始化”操作实际上只是二次赋值操作,所以还不如在初始化列表中直接进行初始化操作。
-
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
-
类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量,引用必须在定义的时候就进行初始化,如果你不在初始化列表中对引用进行显示初始化,初始化列表就会用默认值或者用引用成员变量所在空间的脏值(随机值)对其进行初始化,由于引用不能被重新赋值,后面我们就无法在构造函数中将引用赋值为我们想要的值了,所以引用成员变量一定要写在初始化列表里面进行显式初始化。
- const成员变量,原因同引用(const变量必须在定义的时候就进行初始化,且不能被重新赋值)
-
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值//选D
何时使用编译器自动生成的无参默认构造函数就够了,何时需要自己实现构造函数呢?
如果一个类中的成员全是自定义类型,或者基本类型成员在声明时已经给了缺省值,即无需对基本类型成员变量做处理,且自定义成员都提供了默认构造函数,此时使用编译器自动生成的无参默认构造函数就可以了。
如果有基本类型的成员变量且需要显示传参初始化,即我们需要自己对基本类型成员变量进行处理,或者自定义类型成员没有默认构造函数,那么就要自己实现构造函数。