列表初始化的特性来源于单参数的隐式类型转换。以下面这个赋值为例,我们可以理解成
- 先创建一个匿名对象Point(2),这个时候就变成了 Point p = Point(2);
- 然后会调用拷贝构造。
虽然隐式转换的可以这样理解,但是最后会被编译器优化成直接调用有参构造(下面自定义类型列表初始化模块会证明)。
Point p = 2; // Point是一个自定义类
// Point p = Point(2);
// 最后会被编译器优化成: Point p(2);
列表初始化的基本原理和上面类似,只不过是由单参数的隐式类型转换变成了多参数的隐式类型转换。
目录
1、内置类型的列表初始化
(1) 数组
(2) 动态数组
(3) 标准容器
2、自定义类型的列表初始化
1、内置类型的列表初始化
(1) 数组
在C++11以后,即便没有等号也可以初始化。
int arr1[5] = {1,2,3,4,5}; // int arr2[5]{1,2,3,4,5};
int arr2[] = {1,2,3,4,5}; // int arr2[]{1,2,3,4,5};
(2) 动态数组
动态数组也可以使用{ } 来做到列表初始化。这是C++11以后支持的新特性,C++98不支持。
int* arr3 = new int[5]{1,2,3,4,5};
(3) 标准容器
容器使用列表初始化时,本质是将初始化列表转换成了 initializer_list 类型,然后再调用构造函数 vector(initializer_list<T> il) 来进行初始化 。
vector<int> v{1,2,3,4,5}; // 本质是调用了构造函数vector(initializer_list<T> il)
map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};
vector(initializer_list<T> il)的模拟实现大体如下,其中initializer_lis支持使用迭代器访问初始化列表
vector(initializer_list<T> il)
{
// 判断是否需要扩容
// 使用迭代器遍历初始化列表,逐个插入到数组中
initializer_list<T>::iterator it = il.begin();
while(it != il.end())
{
push_back(*it);
it++;
}
}
2、自定义类型的列表初始化
假设有这样一个自定义类型 Point:
class Point
{
public:
Point(int x = 0, int y = 0): _x(x), _y(y)
{
cout << "Point(int x, int y) 被调用了" << endl;
}
private:
int _x;
int _y;
};
我们可以一次给该类的多个成员进行初始化,前提是该类包含对应的有参构造函数。和最开始的理解方式一致,可以理解成先创建一个匿名对象,然后调用拷贝构造。实际上会被编译器优化成直接调用有参构造。
int main()
{
Point p = {1, 2}; // Point p(1, 2);
}
注意:如果要禁止这样的隐式类型转换(列表初始化),可以使用explicit关键字