列表初始化
一、简介
在C语言中,标准支持使用 { } 对数组或者结构体进行初始化,而 C++98 兼容了C语言的这个特性,并且在C++11 中,扩大了列表初始化的范围,使其可用于任何的内置类型和自定义类型。
注:赋值号可要可不要。
struct Time
{
int _hour;
int _min;
int _sec;
Time(int hour, int min, int sec)
:_hour(hour)
,_min(min)
,_sec(sec)
{}
};
int main()
{
//内置类型变量
int b = { 4 }; //也可以写成int b{ 4 },下面的示例也一样
//数组
int arr1[3]{ 5, 8, 9 };
int arr2[6]{ 0 };
//自定义类型
vector<int> v{ 1, 2, 3, 4, 5 };
Time t{ 19, 25, 31 }; //Time t = { 19, 25, 31 };
pair<int, string> pa{ 8, "8号" };
//列表初始化也可以用于new表达式中
int* ptr = new int[5]{ 0, 1, 8 };
Time* t_ptr = new Time[2]{ { 12, 22, 14 }, { 5, 23, 11 } };
return 0;
}
结果如下:
下面主要讲一下自定义类型的列表初始化。
二、自定义类型的列表初始化
自定义类型的列表初始化可以分为单个对象的列表初始化和多个对象的列表初始化。
1.单个对象的列表初始化
对于 struct,比如 Time t{ 19, 25, 31 };
(即Time t = { 19, 25, 31 };
)
如果结构体没有构造函数,它会走C语言那套进行初始化(兼容C语言),但若有构造函数,列表初始化这里会调用构造函数进行初始化。
具体过程:
由于该构造函数是多参数的构造函数,并且 C++ 支持隐式类型转换,首先会对 { 19, 25, 31 } 调用构造函数来构造出临时结构体,再调用拷贝构造函数,用临时结构体构造出结构体 t 。可以认为编译器进行了优化,直接调用一次构造函数将 { 19, 25, 31 } 构造出了结构体 t 。
对于 class,比如Date d{ 2022, 6, 5 };
(即Date d = { 2022, 6, 5 };
)
如果使用列表初始化,它一定会调用类里的构造函数进行初始化。
具体过程跟 struct 一样。
2.多个对象的列表初始化
如果想要支持某个类的多个对象的列表初始化,给该类添加一个带有 initializer_list 类型参数的构造函数即可。
比如标准容器 vector、list 等,C++11 添加了一个带有 initializer_list 参数的构造函数,所以它们支持多个对象的列表初始化。
其函数声明如下(以 vector 为例,其它的标准容器类似):
//标准容器支持列表初始化
vector<int> v = { 1, 2, 3, 4, 5, 6 };
list<int> lt = { 5, 2, 6, 9, 7 };
map<string, string> dict = { { "insert", "插入" }, { "left", "左边" }, { "template", "模板" } };
其实 initializer_list 是 C++11 新增的一个类模板,整体上跟数组差不多。
initializer_list<int> il = { 1, 2, 3, 4, 5, 6 };
该类模板中主要有三个方法:begin() 、end() 迭代器以及获取元素个数的 size(),想深入了解可以查文档。
由上面的函数声明可以看出,标准容器的列表初始化是:先通过 { } 构造 initializer_list ,再将它作为参数传进构造函数进而完成容器的构造。
同理,如果也想让自己实现的类支持多个对象的列表初始化,只要新增一个带有 initializer_list 类型参数的构造函数即可。
问:列表初始化的 { } 可以为空吗?
答:如果列表初始化的 { } 内为空,它会调用默认构造函数进行初始化。
int a{}; // a是0
int* ptr{}; // ptr是nullptr
Date d{}; // d会调用默认构造函数
vector<int> v; // v会调用默认构造函数
另:列表赋值
此外,标准容器还支持通过 { } 来进行赋值,这是因为 C++11 提供了一个带有 initializer_list 类型参数的赋值重载函数。
其函数声明如下(以 list 为例,其它的标准容器类似):
list<int> lt{ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
lt = { 7, 4 };
同理,如果也想让自己实现的类支持通过 { } 来进行赋值,只要新增一个带有 initializer_list 类型参数的赋值重载函数即可。
总结
列表初始化和列表赋值终究还是要回归到类的主要成员函数的调用,严格上来说没有实质性的变化,不过能够使代码打起来更加方便,看起来也更加简洁。