列表初始化
Published on
列表初始化是使用花括号初始化器对变量进行初始化的方式。C++11之前只能用于数组等少量类型,C++11后将这种方式推广至所有类型。
初始化非类类型
// 不使用列表初始化
int a; // 默认初始化,值不确定
int b = int(); // 零初始化
int c(1); // 直接初始化
int d = 1; // 复制初始化
// 使用列表初始化
int e{}; // 零初始化
int f{1}; // 直接初始化
int g = {1}; // 复制初始化
int h = int{1}; // 直接初始化临时量,然后复制初始化
// 列表初始化引用
const int &r1 = {1}; // 绑定const左值引用到临时变量
int &&r2 = {1}; // 绑定右值引用到临时变量
int &r3 = {2}; // 错误,不能绑定右值到非const左值引用
初始化类类型
初始化聚合类型
聚合类型是下列类型之一:
- 数组类型
- 符合以下条件的类类型
- 没有用户声明或继承的构造函数
- 没有私有或受保护的非静态数据成员
- 没有虚基类
- 没有私有或受保护的直接基类
- 没有虚成员函数
// 聚合类型
struct base1
{
int b1, b2 = 42;
};
// 非聚合类型
struct base2
{
base2() : b3(42) {}
int b3;
};
// C++17里是聚合类型
struct derived : base1, base2
{
int d;
};
使用列表初始化聚合类型相当于使用列表中的元素按顺序初始化该类型中的每个变量。列表可嵌套,但是列表元素个数不能超过聚合类型元素个数。
// 一个复杂聚合类型
struct S
{
int x;
struct Foo
{
int i;
int j;
int a[3];
} b;
};
// 初始化聚合类型
S s1 = {1, {2, 3, {4, 5, 6}}}; // 嵌套列表初始化
S s2 = {1, 2, 3, 4, 5, 6}; // 同s1,进行花括号消除
S s3{1, {2, 3, {4, 5, 6}}}; // 同s1,使用直接列表初始化语法
S s4{1, 2, 3, 4, 5, 6}; // 同s2,使用直接列表初始化语法
// 初始化数组
int ar[] = {1, 2, 3}; // 通过初始化列表推导出数组长度为3
char cr[3] = {'a', 'b', 'c', 'd'}; // 错误,初始化列表长度大于数组长度
char cr[3] = {'a'}; // 初始化列表长度小于数组长度,多出的元素初始化为0
int ar2d1[2][2] = {{1, 2}, {3, 4}}; // 完全花括号
int ar2d2[2][2] = {1, 2, 3, 4}; // 花括号消除
int ar2d3[2][2] = {{1}, {2}}; // 仅初始化第一列,第二列为0
// 初始化std::string数组,其中每个元素可使用不同的初始化方式
std::string ars[] = {std::string("one"), // 复制初始化
"two", // 转换,然后复制初始化
{'t', 'h', 'r', 'e', 'e'}}; // 列表初始化
初始化非聚合类型
对于非聚合类型,寻找最匹配的构造函数对其进行初始化,寻找顺序为:
- 以初始化列表为参数的构造函数
- 以初始化列表中的值作为参数的非explicit构造函数
其中,初始化列表类型std::initializer_list<T>
的对象是一个访问const T
类型对象数组的轻量代理对象。它一般不会手动生成,而是在列表初始化非聚合类型时自动生成。用户可提供以初始化列表对象为参数的构造函数用于自定义列表初始化。
使用列表初始化的优点
- 可替代其它初始化方式,对任何变量进行初始化
- 可接受任意长度数据
- 防止类型窄化。列表初始化对隐式类型转换加以限制,避免了其它初始化方式可能造成的精度丢失