再谈默认构造函数
c++的默认构造函数有三种,分别是:不写编译器默认生成的,自己写的无参构造函数,自己写的全缺省构造函数。
这个编译器默认生成有一个条件:自己必须一个构造函数都不写,否则编译器帮我们生成
不写构造函数不会报错,但是如果没有默认构造的话,就不能用如下方式创建对象。
Date d1(2022,8,20); //自己写的含参构造
Date d2; //调用默认构造函数
那么我们有没有其他方法来进行构造呢?于是c++引入了初始化列表
初始化列表
初始化列表就如上图所示,用:对函数进行初始化
初始化列表的意义
声明定义与初始化
一个变量有声明和定义两个过程。声明就是告诉计算机有这个变量,而定义就是开辟空间的过程。
而初始化可以被理解为是一种赋初值的过程。
而初始化列表可以认为是在定义的同时初始化。
传统的成员变量在对象实例化时定义,在构造函数初始化,那为什么要引入初始化列表呢?
初始化列表价值
因为有些成员变量他不能定义和初始化分离,比如const 修饰的变量,引用类型变量
可以看到直接赋值会报错,因为引用类型必须声明同时定义,如果没有初始化列表,成员对象直接在对象实例化时就声明了,没有定义的过程,所以报错了。
这样写就没有问题。
如果自定义对象作为成员属性时若没有默认构造,也需要初始化列表
我们先创建了一个没有默认构造函数的A类
再写一个以A实例化出对象为成员变量的对象B,默认构造函数中不写A的的构造函数,发现报错。
我们在初始化列表中手动调用a的带参构造就不会报错
而如果给A补上默认构造,也不会报错。
初始化列表的本质
前面我们讲过,在编译器生成的默认构造函数中,自定义类型会调用其对应的构造函数, 非自定义类型不初始化。
这是因为我们无论写不写初始化列表,编译器都会走一遍初始化列表。比如下面的代码
class Time
{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date d(1);
}
哪怕没写Time _t的初始化列表,编译器也会自动调用Time的构造函数
成员变量的声明次序就是初始化列表初始化次序
比如上面这个类,尽管_a先调用初始化列表,但实际上仍然是_aa先初始化。为随机值
隐式类型转换构造函数
先用10生成一个临时的A类对象,然后再利用A的拷贝构造使a完成对象实例化。
但是这样做会有很大问题,我们的单形参构造函数可能不允许我们这样做,于是c++引出了 explicit关键字
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
用一个整形变量给A类型对象赋值
实际编译器背后会用10构造一个无名对象,最后用无名对象给a对象进行赋值。
但是我们给构造函数加上explicit 后就不允许这种隐式类型转换的构造方法。
隐式类型转换的优势
在后期我们会学String类,就可以用
string str1 = "abcdef";
来仅一步简化代码
总结
我们建议,能在初始化列表里初始化,就在初始化列表里初始化。