聚合初始化
从花括号初始化器列表初始化聚合体
目录 |
[编辑] 语法
T object = { arg1, arg2, ...};
|
(1) | ||||||||
T object { arg1, arg2, ...};
|
(2) | (C++11 起) | |||||||
T object = { . designator= arg1 , . designator{ arg2 } ... };
|
(3) | (C++20 起) | |||||||
T object { . designator= arg1 , . designator{ arg2 } ... };
|
(4) | (C++20 起) | |||||||
[编辑] 解释
聚合初始化是一种初始化聚合体的列表初始化
聚合体是下列类型之一:
- 数组类型
- 类类型(典型的为 struct 或 union )
- 无私有或受保护非静态数据成员
- 无用户提供的、继承的或
explicit
(C++17 起)构造函数(允许显式设为默认或删除的构造函数) (C++11 起) - 无虚、私有或受保护的 (C++17 起)基类
- 无虚成员函数
(C++11 起) (C++14 前) |
聚合初始化的效果是:
- 每个直接公开基类、 (C++17 起)数组元素或非静态数据成员,以数组下标/出现于类定义的顺序,从对应的初始化器列表子句复制初始化。
- 若初始化器列表是表达式,则对于每个复制初始化允许隐式转换,除非它们被窄化(如在列表初始化中) (C++11 起)。
- 若初始化器子句是嵌套的花括号初始化器列表(而非表达式),则对应的类成员或公开基类 (C++17 起)被从该子句列表初始化:聚合初始化是递归的。
- 若对象是拥有未知大小的数组,且提供的花括号环绕初始化器列表拥有
n
个子句,则数组的大小是n
- 静态数据成员和无名位域在聚合初始化中被跳过。
- 若初始化器子句数超出要初始化的成员及基类 (C++17 起)数,则程序为病态(编译错误)
|
(C++11 前) |
(C++11 起) |
- 若聚合初始化使用等号形式( T a = {args..} ),则 (C++14 前)环绕嵌套初始化器列表的花括号可消除(忽略),这种情况下,使用所需数量的初始化器子句初始化每个成员,或对应的子聚合体的元素,且后继的初始化器子句被用于初始化对象的后续成员。然而,若对象拥有不带任何成员的子聚合体(空结构体,或只保有静态成员的结构体),则不允许花括号消除,且必须使用空的嵌套列表
{}
。
- 联合体被聚合初始化时,只有其首个非静态数据成员被初始化。
指代初始化器语法形式 (3,4) 被称作指代初始化器:每个 designator 必须指名一个 T 的直接非静态数据成员,而所有用于表达式的 designator 必须与 T 的数据成员以相同顺序出现。 struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // 错误:指代器顺序不匹配声明顺序 A b{.x = 1, .z = 2}; // ok , b.y 初始化为 0 指代初始化器所指名的每个直接非静态数据成员,从后随指代器的对应花括号或等号初始化器初始化。使用用等号的形式时,禁止窄化转换。 指代初始化器可用于初始化联合体到异于首个的状态。只可以为联合体提供一个初始化器。 union u { int a; const char* b; }; u f = { .b = "asdf" }; // OK ,联合体的活跃成员为 b u g = { .a = 1, .b = "asdf" }; // 错误,只可提供一个初始化器 对于非联合聚合体,不提供指代初始化器的元素按如上描述的,在初始化器子句数少于成员数时的规则初始化(若提供则为默认成员初始化器,否则为空列表初始化): struct A { string a; int b = 42; int c = -1; }; A{.c=21} // 以 {} 初始化 a ,调用默认构造函数 // 然后以 = 42 初始化 b // 然后以 = 21 初始化 c 若以指代初始化器子句初始化的聚合体拥有一个匿名联合体成员,则对应的指代初始化器必须指名该匿名联合体的成员之一。 注意:乱序指代初始化、嵌套指代初始化、混合指代初始化器与常规初始化器,及数组的指代初始化在 C 编程语言得到支持,但在 C++ 不允许。 struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // 合法 C ,非法 C++ (乱序) int arr[3] = {[1] = 5}; // 合法 C ,非法 C++ (数组) struct B b = {.a.x = 0}; // 合法 C ,非法 C++ (嵌套) struct A a = {.x = 1, 2}; // 合法 C ,非法 C++ (混合) |
(C++20 起) |
[编辑] 字符数组
字符类型( char
、 signed char
、 unsigned char
、 char16_t
、 char32_t
、 wchar_t
)的数组可以从适当的字符串字面量初始化,可选地以花括号环绕。字符串字面量的相继字符(包含隐式的空终止字符)初始化数组元素。若指定了数组大小,且它大于字符串字面量中的字符数,则剩余字符被零初始化。
char a[] = "abc"; // 等价于 char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // 错误:初始化器字符串太长 unsigned char b[5]{"abc"}; // 等价于 unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // 可选的花括号 // equivalent to wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
[编辑] 注意
聚合类或数组可以包含非聚合公开基类、 (C++17 起)成员或元素,它们以上述方式初始化(例如从对应的初始化器子句复制初始化)
C++11 前,聚合初始化中曾允许窄化转换,但它们不再得到允许。
C++11 前,聚合初始化不能用于构造函数初始化器列表,因为语法限制。
C++14 前,直接列表初始化形式 T a {args..} 不允许花括号消除。
C 中,长度比字符串字面量的大小少一的字符数组可以从字符串字面量初始化;产生的数组是非空终止的。这在 C++ 中不允许。
[编辑] 示例
#include <string> #include <array> struct S { int x; struct Foo { int i; int j; int a[3]; } b; }; union U { int a; const char* b; }; int main() { S s1 = { 1, { 2, 3, {4, 5, 6} } }; S s2 = { 1, 2, 3, 4, 5, 6}; // 相同,但带花括号消除 S s3{1, {2, 3, {4, 5, 6} } }; // 相同,使用直接列表初始化语法 S s4{1, 2, 3, 4, 5, 6}; // C++11 中错误:花括号消除仅允许与等号一同 // C++14 中 OK int ar[] = {1,2,3}; // ar 为 int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // 过多初始化器子句 char cr[3] = {'a'}; // 数组初始化为 {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // 完全花括号的 2D 数组: {1, 2} // {3, 4} int ar2d2[2][2] = {1, 2, 3, 4}; // 花括号消除: {1, 2} // {3, 4} int ar2d3[2][2] = {{1}, {2}}; // 仅第一列: {1, 0} // {2, 0} std::array<int, 3> std_ar2{ {1,2,3} }; // std::array 是聚合体 std::array<int, 3> std_ar1 = {1, 2, 3}; // 花括号消除 OK int ai[] = { 1, 2.0 }; // 从 double 到 int 的窄化转换: // C++11 中错误, C++03 中 OK std::string ars[] = {std::string("one"), // 复制初始化 "two", // 转换,然后复制初始化 {'t', 'h', 'r', 'e', 'e'} }; // 列表初始化 U u1 = {1}; // OK ,联合体首成员 // U u2 = { 0, "asdf" }; // 错误:联合体的过多初始化器 // U u3 = { "asdf" }; // 错误:到 int 的非法转换 } // 聚合 struct base1 { int b1, b2 = 42; }; // 非聚合 struct base2 { base2() : b3(42) {} int b3; }; // C++17 中为聚合 struct derived : base1, base2 { int d; }; derived d1{ {1, 2}, { }, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4 derived d2{ { }, { }, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4