%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 初始化
变量的初始化是指**在构造/创建时提供其初始值**[^1]。
初始值可以在**声明器(declarator)** 或 **new 表达式**的**初始化项(initializer) 部分**提供。
## 初始化项 Initializer
对于每个声明器,初始化项可以是下列四种之一:
> 
- (1) `(expression-list)` :圆括号内可以是 "**逗号分隔的表达式列表**",或者 "**花括号初始化列表**"。
- 例如调用构造函数创建对象时,就是圆括号内的参数列表。
- (2) `= expression`:等号后面跟一个**表达式**,或者**花括号初始化列表**。
- (3) `{initializer-list}`: **花括号初始化列表**(braced-init-list)。
- 花括号内可以为空、逗号分隔的表达式列表、或者其他花括号初始化列表
- (4) `{designated-initializer-list}`:带有指定初始化项的花括号初始化列表
根据不同的上下文,上面四种形式的**初始化项(initializer)** 会执行不同的具体初始化方式。
**==未提供"初始化项"(initializer) 即意味着未被 "显式初始化"==**,对于类类型而言,即是调用 "`non-explicit` 的默认构造函数".
```cpp
int val(0);
int val = 0;
int val = {0};
int val{0};
```
<br>
# 类类型的初始化总结 ⭐
对一个类类型 `T`:
- 如果被 **==默认初始化==**(例如 `T obj;`),则**调用其默认构造函数**。
- 其中,未被显式初始化(既未在"成员初始化列表"中,也没有 "类内初始值")的所有 "非静态数据成员" 将触发**默认初始化**。
- 如果被 **==直接初始化==**(例如 `T obj{arg...}`),则**调用其对应的构造函数**。****
- 其中,未被显式初始化(既未在"成员初始化列表"中,也没有 "类内初始值")的所有 "非静态数据成员" 将触发**默认初始化**。
- 如果被 **==值初始化==** (例如 `T obj{}`),则
- 如果只有 "**编译器提供的默认构造函数**"(自动提供或通过 `= default` 声明),则执行 **==零初始化==**;
- 如果有 **"user-provided" 的默认构造函数**,则**调用该默认构造函数**。
- 如果被 **==零初始化==**,则
- 
- 对于其 "**非静态数据成员**",包括两个阶段:**首先被==零初始化==**(递归地) ,然后**再根据 "初始化项"进行覆盖**:
- 对于**基本类型/数组类型**,**首先执行==零初始化==**,**若指定了 "==默认初始值==" 则对其进行覆盖**;
- 对于**类类型**,**==首先递归==地执行 "==零初始化=="**,而后**根据其初始化项形式调用相应的构造函数**(例如默认构造函数),**==构造函数中的初始化行为会进行覆盖==**。
> [!summary] 唯一特殊的是对类类型进行 "**值初始化 => 零初始化**" 的情况,存在对所有非静态数据成员 "**==递归进行零初始化==**" ,再根据类中声明的 "初始化项" 进行初始化覆盖的过程
> [!NOTE]
>
> - 如果类 `T` 中包含有一个类类型的成员 `a`,且该类类型成员 `a` 只有唯一的构造函数,则任何 `T` 的构造函数都必须要能够对 `a` 进行显式初始化(包括使用成员初始化列表,或者直接指定默认成员初始化项)
> - 对于类类型的**静态成员变量**,与类对象示例的默认初始化以及默认构造函数无关,而是属于 "静态存储持续性的非局部变量"的初始化,包括静态初始化和动态初始化两个阶段:
> - 静态初始化在程序执行任何语句之间(通常是编译时)完成,执行**==常量初始化或零初始化==**(取决于该常量静态成员是否被**指定常量初始值**);
> - 动态初始化阶段通常在进入 `main` 函数执行,根据静态成员变量**被指定的初始值进行直接初始化**。
#### 说明示例
下例中:
- `MyClassD objD1;` 为**默认初始化语法**,故执行 "默认初始化"。
- 由于其只存在 "**编译器提供的默认构造函数**"(空函数),故对所有未被覆盖的成员变量,**均递归采用默认初始化**。
- 其类类型的成员变量 `objA`、`objB`、`objC` 都会执行相应的构造函数,而**其中未被构造函数覆盖的成员**,**==初始值均为未定义的==**。
- `MyClassD objD2{};` 为**值初始化语法**,由于其**不存在用户定义的默认构造函数**,故执行 "**零初始化**"。
- 其所有非静态数据成员均会被首先执行 "**==零初始化==**",然后再**根据初始化项的形式**,**使用 "默认值" 或调用 "构造函数" 进行覆盖**。
- 其类类型的成员变量 `objA`、`objB`、`objC` 首先**会被递归地进行==零初始化==**,**再执行==相应的构造函数==** 覆盖初始化行为。
- `objA`、`objB`、`objC` 中未被构造函数覆盖的数据成员,**均被==递归==地执行了==零初始化==**。
```cpp
struct MyClassA {
MyClassA(): v2(20) {}
int v1 = 10;
int v2;
char ch1 = 'A';
char ch2;
char* ptr;
};
struct MyClassB {
int v1 = 10;
int v2;
char ch1 = 'A';
char ch2;
char* ptr;
};
struct MyClassC {
MyClassC(int v): v1(v) {}
int v1 = 10;
int v2;
char ch1 = 'A';
char ch2;
char* ptr;
};
struct MyClassD {
MyClassA objA;
MyClassB objB;
MyClassC objC {99};
int v1 = 10;
int v2;
char ch1 = 'A';
char ch2;
char* ptr;
};
int main() {
// 默认初始化.
// objD1->objA与objD1->objB, 根据其初始化项形式, 会被递归地执行"默认初始化".
MyClassD objD1;
// 值初始化=>零初始化
// objD2的所有成员都会被"零初始化", 包括objD2->objA与objD2->objB也首先会被递归地零初始化.
// 然后, 再根据各成员的初始化项形式, 使用默认初始化值覆盖, 或者调用对应的构造函数.
MyClassD objD2{};
return 0;
}
```
<br><br>
# 初始化机制说明 ⭐
- 对于 "**自动存储持续性**" 变量,初始化通常在程序执行到其定义语句时进行。(除了**以 `constexpr` 声明的变量将在==编译时==确定值**)
- 对于 "**非局部的静态或线程存储持续性变量**"、以及 "**局部静态或线程存储持续性变量**",初始化机制较为特殊,如下文所述。
> [!quote] 参见[^8]
> - Global object 的内存保证会在程序启动时被**清零**;
> - Local object 配置于程序的栈区中,heap object 配置于程序的堆区,其**内容将是内存上次使用后的遗留值**。
>
<br>
## (1)对「非局部的静态或线程存储持续性变量」的初始化
> 参见 [^1] [^7]
- 具有 **==静态==存储持续性**的 "**非局部变量**" 会在**程序启动时、在main 函数被执行前**就完成初始化(除非延迟)。
- 包括全局变量、命名空间作用域变量、类的静态成员变量;
- 具有 **==线程==存储持续性**的 "**非局部变量**" 会在**线程启动时、在线程函数被执行前**就完成初始化。
对于上述两类变量,会经历下列**两个初始化阶段**::
- (1)**==静态初始化==(Static Initialization)**
- 发生在 "**程序未执行任何代码之前**",只会执行**常量初始化**或者**零初始化**。
- (2)**==动态初始化==(Dynamic initialization)**
- 发生在 **"所有静态初始化完成之后、进入 `main` 函数之前或进入线程函数之前**,根据变量声明中的**指定值或表达式进行初始化**。
> [!summary]
> - 在 "**静态初始化阶段(编译期)**":
> - 若能执行 **==常量初始化==**,则 **==初始化在该阶段完成==**,不再有运行时开销;
> - 否则,变量在该阶段被执行 "**==零初始化==**",随后进入 "**动态初始化阶段**"。
> - 在 "**动态初始化阶段**"(程序启动时): 根据变量的 **"初始化式"(Initializer)形式执行相应初始化行为**。
>
<br>
### 静态初始化
静态初始化**确保**在**程序开始执行之前**,所有非局部的静态和线程变量**已具有一个确定的值**(常量初始化 or 零初始化)
- 若变量类型是 "**字面值类型**"(基本数据类型、指针类型、枚举类型),且其初始式是**常量表达式**,将执行 **==常量初始化==**。
- 通常在==**编译时**==进行,编译器计算得到对象表示并保存在 `.data` 中。
- else,若其无 "**初始化项**",则 **被执行==零初始化==**。
- 变量被存放在程序映像的 `.bss` 段中,该段不占用磁盘空间,并且**在加载程序时由操作系统将其归零**。
<br>
### 动态初始化
动态初始化发生在 **"所有静态初始化完成之后**,**进入 `main` 函数 or 线程函数之前**"(分别对于非局部的静态变量与线程变量)。
该阶段**根据变量定义中 "Initializer" 的形式采用相应的初始化方式(例如默认初始化、值初始化、直接初始化等)**,包括需要调用构造函数、或依赖于运行时计算的非常量表达式或函数返回值的情况。
动态初始化的**执行顺序**有以下几种情况:
- 对于**未被显式特化(explicitly specialized)** 的**静态/线程存储持续性**的类模版中的静态数据成员、以及变量模版,将执行 "**无序的动态初始化**" (Unordered)
- 对这类"静态变量",其初始化顺序相对于所有其它动态初始化而言是不确定的,除非程序在变量被初始化之前启动了一个线程,这情况下,其变量初始化顺序是不确定的。
- 对这类"线程变量",其初始化顺序相对于所有其它动态初始化而言是不确定的。
- 对于 **所有非隐式或显式实例化的特化** 的**内联变量**,将执行 "**部分有序的动态初始化**"(Partially-ordered)
- 如果**一个`partially-ordered`的变量 `V`** 在每个翻译单元中都 **定义在一个`ordered ` 或 `partially-ordered` 的变量`W` 之前**,则 `V` 的初始化排在 `W` 之前。
- 对于 **==所有其它的非局部静态/线程变量==**,将执行"**==有序的动态初始化==**"(Ordered)
- 在单个翻译单元内,将按照**变量在源码中==定义==的顺序**有序地初始化;
- 在不同翻译单元之间,初始化顺序是不确定的。
> [!example] 动态初始化示例一:
>
> ```cpp
> int initValue(int n) {
> return n*n;
> }
> // 两阶段初始化
> // 1) 在程序启动时,未执行任何代码之前, 进行"静态初始化" => "零初始化", 初始化为0值.
> // 2) 所有静态初始化完成之后, 进入`main`函数之前, 进行"动态初始化" => "直接初始化", 根据函数返回值
> int globalVar = initValue(32);
>
> int main() {
> }
> ```
>
> [!example] 动态初始化[示例二](https://stackoverflow.com/questions/5945897/what-is-dynamic-initialization-of-object-in-c/5945936#5945936):
>
> ```cpp
> int cinInit() { // 该函数将在`main()`函数之前被调用两次
> int x;
> cin >> x;
> return x;
> }
>
> // 两个具有静态存储持续性的全局变量包含两个初始化阶段:
> // 1) 在程序未执行任何语句之前, 静态初始化 => 零初始化, 两个变量初始化为0
> // 2) 在所有静态初始化完成之后, `main`函数执行之前, 进行动态初始化.
> // 在动态初始化阶段, 这两个变量将根据其定义顺序, 先后调用`cinInit()`获取函数返回值并完成初始化.
> int globalVar = cinInit();
> const int cstGlobalVar = cinInit();
>
> int main() {
> cout << globalVar << '\n';
> cout << cstGlobalVar << '\n';
> }
> ```
<br>
#### 早期动态初始化(Early Dynamic initialization)
在某些条件[^1]下,**编译器能够将变量的"动态初始化"作为"静态初始化"的一部分在编译时直接进行**。
因此,**当尝试使用==某一个全局变量的值==来==初始化另一个全局变量==时,需注意"定义顺序"**,避免下述的**未定义行为**:
```cpp
inline double fd() { return 1.0; }
extern double d1;
// 初始化d2需要使用d1的值, 而d1定义在d2之后, 因此初始化d2时的行为是"不确定"的、属于未定义行为.
// 分析如下:
// 由于fd()是一个内联函数,返回一个常量值(1.0),因此d1:
// - 可能在静态初始化阶段被附带执行"早期动态初始化", 即在编译时被初始化为1,
// - 可以在静态初始化阶段被初始化为0, 而动态初始化阶段才被初始化为1.
// 因此, d2的初始化可能有以下几种情况:
// 1) d2在动态初始化阶段被初始化为0. 该情况下, d1在静态初始化阶段被初始化为0, 同时由于d1的定义顺序在d2之后, 因此 d2先进行动态初始化, 取得了d1当时的0值, 随后轮到d1动态初始化为1.
// 2) d2在动态初始化阶段被初始化为1. 在该情况下, d1在静态初始化阶段(在编译时)被常量初始化为1, 然后根据定义顺序, d2先进行动态初始化, 取得d1当前的1值.
double d2 = d1; // the value of d2 is unspecified.
double d1 = fd(); // may be initialized statically or dynamically to 1.0
```
为了避免这种不确定性和潜在的未定义行为,可以调整变量的定义顺序,**确保在使用全局变量进行初始化之前,这些变量已经被定义和初始化**。
```cpp
inline double fd() { return 1.0; }
double d1 = fd(); // 无论d1是在编译时(静态初始化阶段)还是在运行时(动态初始化阶段)被初始化为1
double d2 = d1; // 这一声明顺序确保d2一定会在动态初始化阶段"使用d1已被动态初始化后的值"进行初始化
```
<br>
#### 延迟动态初始化
对于具有静态或线程存储持续性的非局部变量而言,**动态初始化是发生在 main函数的第一个语句之前(对于静态变量)或发生在线程的初始化函数之前(对于线程变量),还是延迟到之后发生**,是由编译器具体实现定义的。
具体参见 [Defered Dynamic Initialization](https://en.cppreference.com/w/cpp/language/initialization#Deferred_dynamic_initialization)
<br><br>
## (2)对 「局部静态或线程存储持续性变量」的初始化
**块作用域中声明的局部静态/线程存储持续性变量**,其**初始化将在程序==首次执行到其定义处时==进行**(除非是零初始化或常量初始化,将在首次进入块作用域之前执行),**在此后的所有调用中则会==跳过该初始化语句==**。
局部静态变量无显式初始值时,将被执行 "**==值初始化==**" [^5] (P185, P262)。
> [!caution] 如果**初始化抛出异常,则该变量仍是未初始化的,下次再执行到该初始化语句时将再次尝试初始化**。
```cpp
#include <iostream>
void initFuncLocalStaticVar() {
static int localStaticVar = 255; // 只会在首次执行过该语句时完成初始化, 后续都会跳过
std::cout << localStatic << '\n';
--localStatic;
}
int main() {
initFuncLocalStaticVar(); // 输出: 255
initFuncLocalStaticVar(); // 输出: 254
initFuncLocalStaticVar(); // 输出: 253
initFuncLocalStaticVar(); // 输出: 252
initFuncLocalStaticVar(); // 输出: 251
}
```
> [!NOTE]
>
> - **如果初始化语句"递归地"进入正在初始化变量的块**,则该行为是未定义的。
> - **如果多个线程试图同时初始化相同的静态局部变量**,则 **==初始化只发生一次==**。C++11 起,保证 "静态局部变量初始化" 的 "**线程安全性**"。
> - 对于使用 `std::call_once` 的任意函数,可获得类似行为。
<br><br><br>
# 初始化类型
- **默认初始化** [Default Initialization](https://en.cppreference.com/w/cpp/language/default_initialization)
- **值初始化** [Value Initialization](https://en.cppreference.com/w/cpp/language/value_initialization)
- **直接初始化** [Direct Initialization](https://en.cppreference.com/w/cpp/language/direct_initialization)
- **拷贝初始化** [Copy Initialization](https://en.cppreference.com/w/cpp/language/copy_initialization)
- **列表初始化** [List initialization](https://en.cppreference.com/w/cpp/language/list_initialization)
- **聚合初始化** [Aggregate initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization)
- **零初始化** [Zero Initialization](https://en.cppreference.com/w/cpp/language/zero_initialization)
- **常量初始化** [Constant Initialization](https://en.cppreference.com/w/cpp/language/constant_initialization)
- **引用初始化** [Reference initialization](https://en.cppreference.com/w/cpp/language/reference_initialization)
```cpp
std::string s; // 默认初始化
std::string s{}; // 值初始化
std::string s("hello"); // 直接初始化
std::string s = "hello"; // 拷贝初始化
std::vector<int> arr{1, 3, 5} // 列表初始化
char a[3] = {'a', 'b'} // 聚合初始化(只是用了初始化列表的初始化项形式)
char& c = a[0]; // 引用初始化
```
<br><br>
# 默认初始化 (Default Initialization)
默认初始化:当需要**创建一个对象但没有提供 initializer** 时,执行默认初始化。
##### 默认初始化的触发语法

##### 默认初始化的触发场景
- (1) 当具有**自动**、**静态**或**线程**存储持续性的「**变量(包括数组)**」 **被定义但未提供初始化项**时;
- (2) 当使用 `new` 创建一个具有**动态**存储持续性的对象但**未提供初始化项**时。
- (3) 一个构造函数中,**基类**或者**非静态数据成员**未被 "**构造函数初始化列表**" 显式初始化时。
- 例如:一个类使用了编译器提供的默认构造函数,则实例化该类对象时,**其中的类类型成员变量将被执行默认初始化**。
##### 默认初始化的作用效果
- 对**类类型**(class type),调用具有空参数列表的 **==默认构造函数==**。
- 若**没有默认构造函数则**会报错,包括以下两种情况
- 声明默认构造函数 `= delete`;
- 声明了其它形式的构造函数,但没有默认构造函数;
- 对于**数组**类型,为数组中的**每个元素执行==默认初始化==**。
- **==其它任何情况,不执行初始化==**。
- 对于具有 **自动存储持续性** 的基本数据类型(int, char, float, pointer 等)而言,**其初始值因而都是随机、未定义的**。
- 对于具有 **静态、线程存储持续性** 的变量,由于其初始化过程分为两个阶段,**在静态初始化阶段会被零初始化**,在动态初始化阶段由于未提供 initializer 于是被默认初始化(此时默认初始化的行为就是不执行任何初始化操作)因此最终**零初始化值得到了保留**,所以最终变量是零初始化。
> [!caution] "引用" 和 "常量标量对象"(const scalar object) 不能被默认初始化。
示例:
```cpp
#include <string>
#include <array>
class MyClass {
public:
static int g_var;
int a;
double b;
char ch;
std::string s;
// 默认构造函数:
// 对成员变量`a`,`b`, `ch`均未显式初始化, 因此值为随机;
// 对`s`调用其默认构造函数得到空字符串
MyClass(){}
};
struct T1 { int mem; };
struct T2 {
int mem;
T2() {}; // "mem" is not in the initializer list
};
// 对"Non-local"变量, 初始化发生在两个阶段, 第二个阶段为默认初始化.
int n; // static non-class, a two-phase initialization is done:
// 1) zero-initialization initializes n to zero
// 2) default-initialization does nothing, leaving n being zero.
//
int global_array[10]; // 默认初始化 => 数组内所有元素进行零初始化, 初始化为0;
// 对类中的静态变量
int MyClass::g_var; // 默认初始化 => 进行零初始化, 初始化为0;
int main() {
// 默认初始化的具体行为: 三种情况
std::string s; // 1): 对类类型, 调用其默认构造函数, 得到空字符串"".
std::string arr[2]; // 2): 对数组, 为其中每个元素进行默认初始化.
int a; // 3): 其它类型, 不执行任何初始化 (non-class, the value is inderterminate)
// 更多示例:
int ari[10]; // 默认初始化: 对arr中各个int元素进行默认初始化, 其值是随机的.
int *ptr; // 默认初始化: 不执行初始化, ptr指向的地址是随机的
int *ptr1 = new int; // 默认初始化, 值为随机的
int *ptr2 = new int[5]; // 默认初始化, 数组内元素均被默认初始化, 值是随机的.
T1 t1; // 默认初始化: 对类类型, 调用其默认构造函数
T1 *ptrT = new T1; // 默认初始化: 对类类型, 调用其默认构造函数
// array是唯一一个, 会执行默认初始化的STL容器.
std::array<int, 5> stl_arr; // 默认初始化, 5个元素的值均是未定义的、随机的.
// 常量的情况: T1没有
// const int n; // error: a const non-class. 常量必须在定义时显式地初始化
// const T1 tt1; // error: const class with implicit default ctor.
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
// 对静态局部变量(static local variables)
static int local_var;
}
```
<br><br>
# 值初始化 (Value initialization)
值初始化:当需要**创建一个对象但提供了==空的 initializer==** 时,执行值初始化。
##### 值初始化的触发语法

> [!info] 所有 STL 容器在 **通过"指定大小"的单参数构造函数创建**时,均对其中元素 **==采用 "值初始化"==**。
##### 值初始化的触发场景
- (1)(5):**当使用一对==空的圆括号或花括号==创建一个无名临时对象时**;
- `T()` 或 `T{}`,例如传递一个临时对象作为函数实参时
- (4):当使用一对==**空的花括号**== 来初始化一个具有**自动**、**静态**或**线程局部**存储持续性的变量时;
- `T object{};`
- (2)(6):当使用`new` 创建一个具有**动态存储持续性**的对象且提供了**一对==空的圆括号或花括号==初始化项**时;
- `new T()` 或 `new T{}`;
- (3)(7): 当使用**成员初始化列表**来初始化 **==非静态数据成员或基类==**,但**提供了一对==空的圆括号或花括号==初始化项**时;
- `Class::Class(...) : member() {...}` 或 `Class::Class(...) : member{} {...}`
**例外情况**:
- 在任何形式下,如果使用空大括号 `{}` 并且 T 是 "**==聚合类型==**",则执行「**聚合初始化**」而不是值初始化。
- 如果类类型 **`T` 没有默认构造函数、但具有一个==接收 `std::initializer_list` 的构造函数==,则执行 「列表初始化」**。
##### 值初始化的作用效果
- 对于类类型:
- 若类类型具有一个 "**user-provide" 的默认构造函数**,则调用**该默认构造函数**。
- 若类类型具有一个 "**==编译器提供==的默认构造函数**",则该类类型将被执行 "**==零初始化==**"。
- 包括两种情况:
- (1)**未声明任何构造函数**,则编译器自动生成了一个默认构造函数(空函数)
- (2)通过 `= default` 显式声明让编译器提供一个默认构造函数.
- 若类类型的**默认构造函数不存在**,则报错。
- 包括两种情况:
- (1)**未定义默认构造函数,但定义了其他构造函数**;
- (2)显式声明默认构造函数为 `=delete`
- 对于数组类型,对数组中的**每个元素执行 "值初始化"**。
- **其它任何情况,执行==零初始化==**。
示例:
```cpp
int a; //随机
// int a(); // 错误
int a{};
int a[10];
int a
MyClass obj1;
MyClass obj1 = ();
MyClass obj1 = {};
MyClass obj1{};
int main() {
vector<int> vec; // 空容器, 未进行任何初始化
vector<int> vec(10); // 值初始化 => 零初始化
// array是唯一一个, 会执行默认初始化的STL容器.
array<int, 5> arr; // 默认初始化, 5个元素的值均是未定义的、随机的.
array<int, 5> a00 = {}; // 值初始化 => 零初始化; 对于基础类型, 所有元素保证被初始化为0.
array<int, 5> a000{}; // 值初始化 => 零初始化; 对于基础类型, 所有元素保证被初始化为0.
array<int, 5> a1 = {2, 3, 4}; // 前3项元素直接初始化, 后2项"值初始化", 为0.
array<int, 5> a2(a1); // 复制初始化
array<int, 5> a22 = a1; // 复制初始化
}
```
<br><br><br>
# 零初始化 (Zero initialization)
零初始化:**将对象的初始值设置为 0**。

> [!NOTE] "零初始化"只是其它初始化方法的一种**具体执行方式**,并没有自己的 "syntax"。
##### 零初始化的触发场景
- (1):具有 **==静态或线程存储持续性==** 的、**未被执行"常量初始化"** 的具名变量,将在执行其它任何初始化方式之前,首先被**执行零初始化**。
- (2):作为 **非类类型** 和 **被值初始化但没有默认构造函数的类类型成员** 的值初始化序列的一部分,以及没有提供初始化项的聚合元素的值初始化。
- (3):当使用一个较短的"**字符串字面值**" 来初始化字符数组时,字符数组剩余部分将被执行零初始化。
##### 零初始化的作用效果
- 对于 `scalar type`: 对象被初始化为 "**将==整型字面值 `0` 显式转为 `T` 类型==**"时所取得的值;
- 对于 `non-union` 的 **==类类型==**:
- 
- 所有 **"padding bits" 被初始化为 0 bits**。
- 每个 **"非静态数据成员" 被零初始化**(递归)
- 每个 **"非虚的基类的子对象" 被零初始化**(递归)
- 如果该对象不是一个基类的子对象,则**每个虚基类的子对象将被零初始化**;
- 对于 `union` 类型:
- 所有 "padding bits" 被初始化为 0 bits。
- 对象的**首个非静态具名数据成员**被执行零初始化。
- 对于**数组**类型:数组中每个元素被**零初始化**
- 对于**引用**类型,**==不执行任何操作==**。
<br><br><br>
# 常量初始化(Const initialization)
常量初始化==**在编译时**==完成,将**静态变量的初始值**设置为一个**编译时常量**。
常量初始化仅针对 "**静态或线程存储持续变量**",是对这类变量进行初始化的方式之一。(另一种是零初始化)
##### 常量初始化的触发语法
当一个**静态/线程变量的完整初始化**表达式是 "**常量表达式"(即可在编译时确定值)** 时,将对其进行 "**常量初始化**"。
```cpp
#include <iostream>
#include <array>
struct S
{
static const int c;
};
// not a constant expression: S::c has no preceding
// initializer, this initialization happens after const
const int d = 10 * S::c;
// constant initialization, guaranteed to happen first
const int S::c = 5;
int main()
{
std::cout << "d = " << d << '\n';
std::array<int, S::c> a1; // OK: S::c is a constant expression
// std::array<int, d> a2; // error: d is not a constant expression
}
```
<br><br><br>
# 直接初始化(Direction initialization)
通过 **==显式的构造函数参数集==** 初始化对象。
##### 直接初始化的触发语法

- (1):使用**非空的圆括号或圆括号表达式**列表进行初始化;
- (2):对非类类型使用单个花括号扩起的初始化项进行初始化;
- (3):使用**函数样式(function-style)的类型转换** 或 **圆括号表达式列表** **对一个右值结果进行初始化**
- (4):使用静态强制类型转换 `static_cast<T>` 对一个右值进行初始化;
- (5):使用带有初始化项的 new-expression 初始化具有动态存储持续时间的对象。
- (6):在构造函数中使用非空的成员初始化列表对非静态成员进行初始化
- (7):在 lambda 表达式中**复制**捕获的外部变量,初始化用于函数闭包内的同名变量
##### 直接初始化的作用效果
- 如果 `T` 是非类类型,而源类型是类类型,则**检查源类型及其基类的转换函数**,如果存在,则根据用户定义的转换将初始化表达式转为需要被初始化的对象。
- 对于类类型 `T` :**调用 `T` 的相匹配的构造函数来构造/初始化对象**
- 如果目标类型是一个**聚合体** (aggregate class),则**基本与 "聚合初始化" 一样,除了**:
- 允许缩窄转换(聚合初始化不允许)
- 不允许 "desinated "初始化项(聚合初始化允许,用在初始化列表 `{}` 中)
- 被绑定到一个"引用"的临时对象,**其生命周期==不会被延长==**(列表初始化则可以)
> [!example] 对于 "聚合体" 的直接初始化(不推荐这么用)
>
>
> ```cpp
> struct B {
> int a;
> int&& r;
> };
>
> int f() { return 20; } // 返回了一个临时对象;
> int n = 10;
>
> B b1{1, f()}; // 列表初始化: f()返回的临时对象被赋给`r`, 其的生命周期得到延长,
> B b2(1, f()); // well-formed, but dangling reference
>
> B b3{1.0, 1}; // error: 列表初始化中不允许缩窄转换
> B b4(1,0, 1); // 直接初始化, 允许缩窄转换. well-formed, but dangling reference
>
> B b5(1.0, std::move(n)); // OK
> ```
>
示例:
```cpp
#include <iostream>
#include <memory>
#include <string>
struct Foo
{
int mem;
explicit Foo(int n) : mem(n) {}
};
int main()
{
std::string s1("test"); // constructor from const char*
std::string s2(10, 'a');
// 直接初始化
std::unique_ptr<int> p(new int(1)); // OK: explicit constructors allowed
// 拷贝初始化, 不能使用`explict`的构造函数
// std::unique_ptr<int> p = new int(1); // error: constructor is explicit
// 直接初始化
Foo f(2); // f is direct-initialized:
// constructor parameter n is copy-initialized from the rvalue 2
// f.mem is direct-initialized from the parameter n
// 拷贝初始化, 不能使用`explict`的构造函数
// Foo f2 = 2; // error: constructor is explicit
std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem << '\n';
}
```
<br><br><br>
# 拷贝初始化(Copy initializtion)
使用**一个对象**,初始化**另一个对象**。
##### 拷贝初始化的触发语法

##### 拷贝初始化的触发场景
- (1) 使用由"**等号 `=` 以及一个表达式**" 组成的"初始化式" (Initializer) 初始化一个**非引用类型的具名变量**时
- (2) 与上一条相同,但使用花括号括起来,属于 **"拷贝列表初始化"**,花括号列表中 **==禁止缩窄转换==**;
- (3) **==函数调用传参==** 时(形参为 **"按值传递"**,而非引用或指针时)
- 函数调用时,非引用/非指针类型的参数**允许接受一个可进行隐式转换的实参**;
- (4) **==函数调用返回值==** 时(返回类型为 **"按值传递"**,而非引用或指针时)
- 函数返回非引用/非指针类型的值时,**允许返回一个可进行隐式转换的对象**;
- (5) 当 **==抛出或捕获一个异常==** 时(以 **"值"的形式** )
- (6) 作**为聚合初始化**的一部分,为初始化列表中 **"提供了初始化项的元素进行初始化**时
##### 拷贝初始化的作用效果
> [!summary] 拷贝初始化总结
>
> 在拷贝初始化中,编译器会搜索**能够将 `other` 类型转换为 `T` 类型**的途径,包括几种情况:
>
> - (1)`T` 类型具有**以 `other` 类型为参数**的 `non-explicit` 构造函数—— ==**转换构造函数**== or **==拷贝/移动构造函数==**;
> - (2)`other` 类型具有能够**转换到 `T` 类型**的 "**==转换函数==**"。
> - (3)**标准隐式转换**(针对 T 与 other 均不是 "类类型" 的情况)
>
>
> 对于情况(2),理论上**调用 `other` 的转换函数**首先得到一个 **T 类型的临时对象**,再通过调用 `T` 的拷贝(或移动)构造函数来完成初始化。
> 实际上,**受编译器优化**,**转换结果将直接构建在目标对象的内存地址**,不存在调用 "**拷贝 or 移动构造函数**" 的过程。
> C++17 起,明确规定了 "**拷贝省略**"(Copy Elision)的特性,明确规定采取上述做法。
```cpp
struct A {
A() {} // 默认构造函数, 也属于转换构造函数. converting constructor (since C++11)
A(int) {} // 单参数构造函数, 是转换构造函数. converting constructor
A(int, int) {} // 转换构造函数. // converting constructor (since C++11)
};
struct B {
explict B() {}
explict B(int) {}
explict B(int, int) {}
};
int main() {
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3{4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast, direct-initialization
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3{4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization selected an explicit constructor
// B::B(int, int)
B b5 = (B)1; // OK: explicit cast performs static_cast, direct-initialization
B b6; // OK, default-initialization
B b7{}; // OK, direct-list-initialization
// B b8 = {}; // error: copy-list-initialization selected an explicit constructor
// B::B()
[](...){}(a1, a4, a4, a5, b5); // may suppress "unused variable" warnings
}
```
<br><br>
## 直接初始化与拷贝初始化的区别
> 参考 [^2] [^3] [^4],[^5](P76)
> [!summary] 直接初始化与拷贝初始化的区别
>
> - **直接初始化**:形式为 `T t(e)`
> - 编译器会考虑 **==所有构造函数和用户定义转换函数==(包括声明为`explicit` 的)**。
>
> >
>
> - **拷贝初始化**:形式为 `T t = e` ,表达式 `e` "**被隐式转换**" 为`T` 类型:
> - 编译器只考虑:
> - **T 类型的`non-explict` 的转换构造函数(包括拷贝、移动构造函数等)**
> - **e 类型的`non-explict` 的用户定义转换函数**"。
>
> [!caution] **"拷贝初始化" 与 "拷贝构造函数" 没有必然关联**
>
> - 从 "**拷贝初始化**" 的角度来看:
> - 拷贝初始化会考虑**所有 `non-explicit` 的构造函数和转换函数**,包括拷贝构造函数、移动构造函数等。
>
> - 从 "**拷贝构造函数**" 的角度来看:
> - `non-explicit` 拷贝构造函数可在 "直接初始化" 或 "拷贝初始化" 的语法下被调用;
> - `explict` 拷贝构造函数则只能在 "直接初始化"语法下调用,**禁止用于 "拷贝初始化"**。
> [!NOTE] **"`explicit` 构造函数或转换函数"** 禁止在拷贝初始化中被调用。
<br><br><br>
# 列表初始化(List-initialization)
列表初始化:通过一个 **花括号初始值列表** 来初始化一个对象的方式。 (since C++11)
包括两种语法形式:**直接列表初始化**、**拷贝列表初始化**
> [!NOTE] "列表初始化" 也叫 "**统一初始化**"(uniform initialization),其可用于任何需要初始化的地方。
<br>
## 列表初始化的触发语法
### 直接列表初始化
语法形式:

- (1):使用 "花括号初始化列表" 初始化**具名变量**
- (2):使用 "花括号初始化列表" 初始化不具名的**临时对象**
- (3):在 `new-expression` 中使用 "花括号初始化列表" 初始化**动态对象**
- (4):在类定义中使用 "花括号初始化列表" 为**类的非静态成员变量** 指定**类内初始化值**。
- (5):在类的构造函数的 "**成员初始化列表**" 中使用 "花括号初始化列表" 初始化类的**非静态成员变量**
> [!NOTE] "花括号初始化列表" 内的内容可以为**空**、为**表达式**、或**嵌套的花括号初始化列表**。
<br>
### 拷贝列表初始化

- (6):在**等号 `=` 之后**使用 "花括号初始化列表" 初始化**具名变量**;
- (7):在**函数调用的参数列表**中,使用 "花括号初始化列表" **作为一个实参**,初始化函数的**形参**
- 注意,是一整个花括号对应一个函数实参,而不是一个花括号内包含全部函数实参。
- (8):在**函数返回语句 `return` 中**,使用 "花括号初始化列表" **作为返回表达式**,初始化**返回对象**
- (9):在用户定义的 **`[]` 操作符**的**下标表达式**(subscript expression)中,使用 "花括号初始化列表" 初始化该**重载操作符`[]`的形参**。
- (10):在**赋值表达式**中,使用 "花括号初始化列表" 初始化**重载操作符 `=` 的形参**。
- (11):在**函数类型转换表达式** 或 **构造函数** 调用中,使用 "花括号初始化列表" **代替构造函数参数**,初始化 `U` 的**构造函数的"参数"**。
- 注:上图中 `U` 不是需要被列表初始化的类型,`U` 的"**构造函数的参数**" 才是被列表初始化的对象。
- 与直接列表初始化中的方式(1)不同,这里是用**列表初始化先去初始化/构造了各个"参数"**,
而**方式(1) 是通过 `{}` 来调用构造函数**,将已有的变量用作"参数"。
- (12):在类定义中使用"等号`=` 和花括号初始化列表" 为**非静态数据成员**指定**类内初始值**。
<br>
## 列表初始化的作用效果
对于类型 `T` 的对象:
- 若 `{}` 为空,执行 "**==值初始化==**"。
- 若 `T` 为**类类型**:
- (1)如果 **`T` 具有默认构造函数且 `{}` 为空**,执行 "**==值初始化==**";
- (2)检查 **`T` 的所有构造函数**,根据重载解析进行匹配,包括两个阶段:
- (2.1)检查 `T` 中 ==**以 `std::initializer_list` 为单参数**==(包括以其作为首项参数且其余参数都有默认值的情况)的**构造函数**;
- (2.2)检查 `T` 的所有"**==参数列表==**" 与 "**==花括号初始化列表内元素==**" 相匹配(只允许窄化转换)的**构造函数**;
- 若存在匹配的构造函数,则检查:
- 若声明为 `explict`,则**不允许"拷贝列表初始化"**,只支持 "**直接列表初始化**" 形式。
- 若无 `explict` 约束,则 "直接列表初始化/拷贝列表初始化" 两种形式都允许。
类类型的列表初始化示例 [^5](P89):
```cpp
vector<int> v1(10); // 直接初始化, v1有10个元素, 每个值均为0(每个元素被值初始化=>零初始化)
vector<int> v2{10}; // 列表初始化, v1有1个元素, 值为10.
vector<int> v3(10, 1); // 直接初始化, v3有10个元素, 值均为1
vector<int> v4{10, 1}; // 列表初始化, v4有两个元素, 值为10和1
```
- 若 `T` 是**聚合类型** (aggregate type):
- 初始化列表中**各初始化项的声明顺序必须对应于 "聚合体" 中的元素顺序**,**执行==聚合初始化==**;
- 初始化列表可以是 "designated" 初始化列表,**执行==聚合初始化==**;
- 如果初始化列表不包含 "designated"项,且**只具有一个与 T 相同类型或是派生类型的对象 V,则使用该元素来进行初始化**:
- 如果是拷贝列表初始化形式 (例如`= {V}` ),则执行**拷贝初始化**;
- 如果是直接列表初始化形式 (例如`T ojb{V}`),则执行**直接初始化**。
```cpp
struct X {};
X x;
X x2 = X{x}; // 拷贝初始化 (而非聚合初始化)
X x3{x}; // 直接初始化 (而非聚合初始化)
```
- 若 `T` 是**字符数组**,且初始化列表中**只包含一个合适的字符串字面量**(大小未超出数组长度),则**等同于直接使用该字符串字面量初始化字符数组**。
- 若 `T` 不是类类型,且**初始化列表只有一项元素**。
- 若 `T` 不是引用类型,or `T` 是一个引用类型且其**所引用的"类型**" **与初始化列表中的元素类型相同,或者是其基类**,
则根据初始化列表的使用方式进行 "**==直接或拷贝列表初始化==**",但 **==禁止窄化转换==**。
```cpp
// r2是一个左值引用, 其所引用的类型是`int`, 与初始化列表中的`2`是相同的int类型.
// 因此这里进行了 "拷贝列表初始化", 使用`2`来初始化r2.
const int& r2 = {2};
```
<br>
## 列表初始化禁止内置类型间隐式的窄化转换
列表初始化**禁止**以下形式的 **==隐式窄化转换==** [^6](Item 7):
- **从浮点类型到整型的转换**:浮点值转换为整型时可能丢失小数部分,这种转换在列表初始化中是禁止的。
- 例如,`int x = {3.14}; ` 将会导致编译错误。
- **从大范围整型到小范围整型的转换**:
- 如果**初始值**超出了目标类型的表示范围,转换是不允许的。
- 例如,`char c = {256};`(假设`char`是 8 位)将会导致编译错误。
- 如果**源类型的范围超出了目标类型的范围**,例如**将枚举成员转为整型,也是禁止的**。
- **从整型到浮点类型的转换,当值无法精确表示时**:
- 如果整数值**无法精确地表示为浮点数**(例如整数太大,超出了浮点数精度范围),则是禁止转换。
- 如果整数是一个常量表达式并且能够被精确存储为浮点型,则允许该隐式类型转换
- 从 **"指针类型" 或 "成员指针类型"** 到 **bool 类型**;
```cpp
void implict_conversions_prohibit() {
// 禁止从浮点类型到整型的转换
// int x = {3.14};
// 禁止从大范围数到小范围数的转换, 包含具有大范围值的枚举类型到小范围值的枚举类型.
// char ch = {256}; // error: Constant expression evaluates to 256 which cannot be narrowed to type 'char'
char ch = {32}; // allowed.
// int var_int = { 0xffffffff }; // error: Constant expression evaluates to 4294967295 which cannot be narrowed to type 'int'
int var_int = { 0xfffffff }; // allowed
enum BigCOLOR { RED, BLUE, GREEN, BLACK };
// BigCOLOR bc = { 3 }; // error: 禁止将整型转为等值的枚举成员
int var_bc = GREEN; // allowed, 枚举成员转int
// 禁止整型到浮点型的、值无法被精确表示的转换. 例如,整数太大,超出了浮点数的精度范围
// double db = { 0xffffffffffffff }; // error
double db = { 0xfffffffffffff }; // allowed. 只要整型值能被浮点型精确表示, 则是可以转换的
}
```
<br>
## `std::initializer_list` 对象
`<initializer_list>` 头文件中,定义为:
```cpp
template <class T>
class initializer_list;
```
初始化列表 `std::initializer_list` 是一个**类模版**,类型为`std::initializer_list<T>` 的对象是一个轻量级**代理对象**,提供了对**一个==类型为 `const T`== 的对象数组** 的"**==引用==**"(不拥有元素,而是提供了对原始数组的引用)
其可以被以实现为**一对指针**或**指针加长度**,**==复制 `std::initializer_list` 对象不会复制其背后的数组==**。
- **原始数组引用**:`std::initializer_list` 不拥有元素,而是提供了对 **` const T` 的对象数组的引用**。
- **只读访问**:`std::initializer_list` 对象本身以及所含元素是只读的,不能修改。
> [!caution] 花括号初始化列表`{ a1, a2, ...}`" 本身只是一个语法形式,并不等同于一个`std::initializer_list`对象
>
> 使用花括号初始化列表时并不意味着创建了一个 `std::initializer_list`,后者只在特定场景下会被自动构造。
>
> - **初始化列表(initializer-list)是一种初始化项的语法形式**,提供了一种统一的方式来初始化数据,即通过一个**花括号列表 (braced-init-list)**;
> - 初始化列表不是一个表达式,也不是一个对象,因此没有类型, `decltype({1, 2})` 是非法的;
>
> - **`std::initializer_list` 是一个有具体类型的对象**,主要用作 "**构造函数的参数**" 、”**函数参数**"、**基于范围的`for`循环**。
>
>
<br>
### `std::initializer_list` 对象的自动构造
`std::initializer_list` 对象只在下列场景使用 "**花括号列表**" 时会被**自动构造**:
- 使用 "花括号列表" 对一个对象进行 "**列表初始化**",且 **==该对象具有 `std::initalizer_list` 类型单参数的构造函数==** 时。
- 使用 "花括号列表" 作为**赋值 `=` 的右操作数** 或 **函数调用参数**,且 **==赋值操作符`operator =` 以及函数接受一个 `std::initializer_list` 类型的参数==** 时。
- 一个 "花括号列表" 被**绑定到 auto 时**,例如用在 range-based `for` 循环中时。
- 编译器将根据提供花括号内提供的值**自动推断出列表中"元素的类型"**,从而**创建一个对应类型的`std::initializer_list`对象**
```cpp
for (int x : {-2, 4, 5, 6}) { ... } // 将创建一个`std::initializer_list<int>`对象
auto al = { 10, 23, 665, 4}; // 将创建一个`std::initializer_list<int>`对象
```
### `std::initializer_list` 的成员
> 
对外只提供三个接口,**==不支持下标访问==**,**==不支持追加元素==**:
- `size()` 获取元素数量;
- `begin()` 与 `end()` 返回迭代器;可以像使用迭代器一样访问其中元素。
### 使用示例
用法一:使用 `std::initializer_list` 作为函数或构造函数的参数,以便接受花括号初始化列表。
```cpp
// 示例一
void print(std::initializer_list<int> vals) {
for (auto p = vals.begin(); p != vals.end(); ++p) {
std::cout << *p << "\n";
}
// 也可以用for 范围循环
for (int value : vals) {
std::cout << value << " ";
}
std::cout << std::endl;
}
```
用法二:让自定义类型能够**使用花括号初始化**,则为其提供一个接受该类的构造函数
```cpp
// 示例二
class MyIntVector {
private:
std::vector<int> data;
public:
// 使用initializer_list直接来初始化vector容器.
MyIntVector(std::initializer_list<int> init) : data(int) {}
};
```
示例三:当 "**指明实参个数**" 与 "**指明一个初值列**" 的构造函数同时存在时,使用花括号初始化将调用后者;
```cpp
class MyClass {
public:
MyClass(int, int);
MyClass(std::initializer_list<int>);
};
MyClass ms(77, 5); // calls MyClass::MyClass(int,int)
// 使用花括号初始化时, 将调用初值列版本
MyClass q{77,5}; // calls MyClass::MyClass(initializer_list)
MyClass r{77,5,42}; // calls MyClass::MyClass(initializer_list)
MyClass s = {77,5}; // calls MyClass::MyClass(initializer_list)
// 如果上述“带有一个初值列”的构造函数不存在,那么接受两个int的那个构造函数会被
// 调用以初始化q和s,而r的初始化将无效。
```
示例四:`explicit` 修饰符能使得"多值的**隐式类型转换**"不生效,在使用赋值初始化 "=" 时也一样
```cpp
class P {
public:
P(int a, int b) {
...
}
explicit P(int a, int b, int c) { // 禁止隐式类型转换
...
}
};
P x(77,5); // OK
P y{77,5}; // OK
P z {77,5,42}; // OK
P v = {77,5}; // OK (implicit type conversion allowed) 允许隐式类型转换;
// P w = {77,5,42}; // ERROR due to explicit (no implicit type conversion allowed)
void fp(const P&);
fp({47,11}); // OK, implicit conversion of {47,11} into P
// fp({47,11,3}); // ERROR due to explicit
fp(P{47,11}); // OK, explicit conversion of {47,11} into P
fp(P{47,11,3}); // OK, explicit conversion of {47,11,3} into P
```
<br><br><br>
# 聚合初始化(Aggregate Initialization)
聚合初始化是 "列表初始化" 的一种形式,用于初始化一个 "aggregate"。
> 
<br>
## 聚合体(Aggregate)
聚合体的定义如下:
- **==数组类型==**(如`int[]`或`int[10]`)
- 具体下述特性的 **`struct` 或 `union`** 关键字声明的 **特殊类类型**:
- 没有用户声明的或继承的**构造函数**
- 没有**私有或保护的"直接"非静态数据成员**
- 没有**虚基类**
- 没有**私有或保护的直接基类**
- 没有**虚成员函数**
聚合体中的 **==元素按顺序排列==**,其元素包括:
- 对数组而言,即**数组元素,按下标递增顺序排列**;
- 对类类型而言,首先是**根据声明顺序排列的直接基类**、随后是根据声明顺序排列的"非匿名位字段或匿名联合体成员"的**直接非静态数据成员**。
聚合初始化根据聚合体内的 "**==元素顺序==**" 初始化其各项元素。
- 如果初始化列表 "**非空且包含 `n` 个初始项**" ,则将初始化聚合体内的 **"前 `n` 项" 元素**。
- 如果初始化列表为空,则**没有任何元素被"显式初始化"**。
<br>
## 聚合初始化规则
- 对于每一个**被显式初始化的元素**:
- 如果元素是一个匿名联合体的成员,且初始化项是一个 "**指定初始化项列表**"(designated initializer list),则用其进行初始化;
- 如果初始化项是一个表达式,则 **==允许隐式类型转换==**(如同拷贝初始化),但==**禁止窄化转换**==
- 如果初始化项是一个**嵌套初始化列表**(这不是表达式),则对**嵌套于内的初始化列表对应元素项进行 "列表初始化"**,如果对应元素是聚合体(即子聚合体),则同样递归地应用上一条规则。
- 嵌套的初始化列表的"花括号"可以省略(Brace elison),因为外层整个是按元素顺序挨个进行初始化的。
- 注:
- 如果嵌套初始化项对应的是**一个没有任何非静态成员的子聚合体**(空结构体,或者仅有静态成员),则不能省略该项对应的花括号,必须显式使用空的花括号`{}`。
- 如果聚合初始化使用的是 "直接列表初始化"的语法(即不带等号 `=` 而直接使用`{}`)
- 对于non-union 聚合体中的每一个**未被显式初始化的元素**:
- 如果该元素具有 "**类内初始值/默认成员初始化项**",则根据该值进行初始化;
- else,如果该元素非引用类型,则**使用空初始化列表 `{}` 对其进行拷贝初始化**,即 `= {}`;
- else,程序格式不正确。
> [!NOTE] 对于数组而言,未被显式初始化的元素,被执行 `= {}` 拷贝列表初始化语法,具体效果即为 "**值初始化=>零初始化**"
- 对于 `union` 聚合体,可采用的初始化手段为:
- 使用 "**非空的初始化列表"** 显式 **"初始化其"首项"元素**;
- 使用 "**空的初始化列表**" `{}`:
- 如果任何变体成员具有默认成员初始化项,则采用该默认初始化;
- 否则,使用空初始化列表 `{}` 对其中的 "**首个**" 成员进行**拷贝初始化**,即 `= {}`。
- 使用 "**designated 初始化列表**",**显式地指定对某项元素进行初始化**。 since C++20
注:下列情况非法
- 初始化列表中**初始化项的数量**超过**聚合体的元素个数**;
```c++
char cv[4] = {'a', 's', 'd', 'f', 0}; // error, 超过聚合体元素个数
```
- 使用空的初始化列表对一个边界未知的数组进行初始化
```c++
int x[] = {}; // error: 数组边界未知
```
<br>
## 指定初始化器 (Designated initializers)
自 C++20 起引入的语法。在初始化列表中使用指示符 "designator" 来显式指明该初始化项针对的元素。
初始化列表中的**指示符顺序 **必须与**聚合体中数据成员的顺序相同**。
```c++
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error; designator order does not match declaration order
A b{.x = 1, .z = 2}; // ok, b.y initialized to 0
```
<br><br>
# 引用初始化(Reference Initialization)
将一个引用绑定到一个对象。
<br><br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: [Initialization - cppreference.com](https://en.cppreference.com/w/cpp/language/initialization)
[^2]: [Is there a difference between copy-initialization and direct-initialization?](https://stackoverflow.com/questions/1051379/is-there-a-difference-between-copy-initialization-and-direct-initialization)
[^3]: [【原创】c++拷贝初始化和直接初始化的底层区别](https://www.cnblogs.com/cposture/p/4925736.html)
[^4]: [[C++面试]拷贝初始化与直接初始化]( https://cloud.tencent.com/developer/article/1919680 )
[^5]: 《C++ Primer》
[^6]: 《Effective Modern C++》
[^7]: [C++ - Standards](https://www.open-std.org/jtc1/sc22/wg21/docs/standards)
[^8]: 《深度探索 C++对象模型》P40