列表初始化,就是使用一对花括号 {} 来初始化对象的一种语法,旨在为各种类型的初始化提供一种统一的语法。

为什么需要列表初始化

        

因为在C++11之前,初始化方式有很多种:

// 比如
int a = 5;  
int b(10);  
int arr[] = {1, 2, 3};
// 对于结构体需要初始化的话,通常要先定义再赋值
struct Point {int x; int y;};
Point p1;
p1.x = 1;
p1.y = 2;
// 或者是另外一种方法
Point p2 = {1, 2};

但是在列表初始化引入后,几乎所有对象的初始化都可以使用 {} ;

int a {5};  // 直接列表初始化
int b = {10};  // 复制列表初始化
int arr[] {1, 2, 3};  // 可以省略等号
Point p {1, 2};
核心优点和特性
1、统一初始化语法:

        几乎所有类型的对象(内置类型、类对象、容器、数组等)都可以使用 {} 进行初始化,就不需要记忆各种类型的初始化。

int a {1};
std::string s {"hello"};
std::vector<int> v {1, 2, 3};
std::map<int, std::string> m { {1, "zhangsan"}, {2, "lisi"} };
2、防止窄化转换(即禁止将数值赋给无法存储它的数值变量)

        编译器会检查 {} 中的值,如果会导致数据丢失(精度降低或溢出),则会报错或警告。可以避免许多忽略的初始化错误

int a = 3.14  // 警告,“初始化”: 从“double”转换到“int”,可能丢失数据
int y = {3.14};  // 错误,从“double”转换到“int”需要收缩转换
// 但是允许转换为更宽的类型
char x {99};  // int-to-char, in range, allowed
double y {99};  // int-to-double, in range, allowed
3、避免歧义
// 在传统语法中,这是声明了一个返回MyPoint的类型的函数,而不是一个对象
MyPoint point1();  // 函数声明,而不是调用默认构造函数

// 使用列表初始化
MyPoint point2();  // 调用默认构造函数
MyPoint point3(arg);  // 调用带参数的构造函数
4、std::initializer_list 协同工作

        如果一个类(通常是容器)有一个接受std::initializer_list 作为参数的构造函数,那么使用{}初始化时只会调用这个构造函数

//v1数组里创建了5个元素, 每个元素值都为10 --》[10,10,10,10,10]
std::vector<int> v1(5, 10); 
//v2数组创建2个元素,5和10
std::vector<int> v2{5, 10};
// v2的初始化等同于:
// std::initializer_list<int> il = {5, 10};
// std::vector<int> v2(il);

   了解std::initializer_list:

注:(如果想了解更多的,可以看看侯捷老师的C++新标准)

std::initializer_list是C++11引入的一个轻量级模板类,定义在<initializer_list> 头文件

我觉得可以把它在概念上看作一个数组容器,但它是只读的、轻量级的、临时存在

工作流程

一:解析:编译器识别 {} 语法

二:创建:在栈上创建临时常量数组

三:包装:使用数组对象构造std::initializer_list对象

四:匹配:只匹配initializer_list构造函数

五:调用:传递initializer_list给构造函数

六:使用:在构造函数内部使用传递来的数据

七:清理:表达式结束后自动清理临时对象

std::vector<int> v{1, 2, 3};
//流程:
// 创建临时数组{1, 2, 3}
// 构造initializer_list<int>
// 调用vect的initializer_list构造函数
// vector内部复制数据到自己的存储当中
注意:
1、auto的推导
auto x {42};  // x类型是int
auto y = {42};  // y类型是std::initializer_list<int>

为了清晰以及代码的可移植性,应该避免使用auto来推导单个值的列表初始化

2、在明确不该使用initializer_list 构造函数时:

如果它的行为不是你想要的,或者是调用特定的构造函数,应该使用传统的圆括号 ();

如:假如你想指定容器的大小和初始值,用();想用一组初始值初始化容器,用 {};

  • std::vector<int> v(10, 20); // 十个元素,每个都是20
  • std::vector<int> v{10, 20}; // 两个元素:10 和 20

更多推荐