%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% # 初始化 变量的初始化是指**在构造/创建时提供其初始值**[^1]。 初始值可以在**声明器(declarator)** 或 **new 表达式**的**初始化项(initializer) 部分**提供。 ## 初始化项 Initializer 对于每个声明器,初始化项可以是下列四种之一: > ![image-20240113160855979|702](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-5F7F907A5AD8610F23E78FFF98751904.png) - (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" 的默认构造函数**,则**调用该默认构造函数**。 - 如果被 **==零初始化==**,则 - ![image-20240115162554829|814](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-80884E716815D8CC681BC1D098E728AC.png) - 对于其 "**非静态数据成员**",包括两个阶段:**首先被==零初始化==**(递归地) ,然后**再根据 "初始化项"进行覆盖**: - 对于**基本类型/数组类型**,**首先执行==零初始化==**,**若指定了 "==默认初始值==" 则对其进行覆盖**; - 对于**类类型**,**==首先递归==地执行 "==零初始化=="**,而后**根据其初始化项形式调用相应的构造函数**(例如默认构造函数),**==构造函数中的初始化行为会进行覆盖==**。 > [!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** 时,执行默认初始化。 ##### 默认初始化的触发语法 ![image-20240109174553873|148](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-58ACCAD7886E388B883EFD95CD9DB891.png) ##### 默认初始化的触发场景 - (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==** 时,执行值初始化。 ##### 值初始化的触发语法 ![image-20240109190819654|431](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-66029E7468D24630AB2F5AFC9196606F.png) > [!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**。 ![image-20240109201821193|785](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-C40C4E18C7567A46F4D175467829CF67.png) > [!NOTE] "零初始化"只是其它初始化方法的一种**具体执行方式**,并没有自己的 "syntax"。 ##### 零初始化的触发场景 - (1):具有 **==静态或线程存储持续性==** 的、**未被执行"常量初始化"** 的具名变量,将在执行其它任何初始化方式之前,首先被**执行零初始化**。 - (2):作为 **非类类型** 和 **被值初始化但没有默认构造函数的类类型成员** 的值初始化序列的一部分,以及没有提供初始化项的聚合元素的值初始化。 - (3):当使用一个较短的"**字符串字面值**" 来初始化字符数组时,字符数组剩余部分将被执行零初始化。 ##### 零初始化的作用效果 - 对于 `scalar type`: 对象被初始化为 "**将==整型字面值 `0` 显式转为 `T` 类型==**"时所取得的值; - 对于 `non-union` 的 **==类类型==**: - ![image-20240115162554829|814](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-80884E716815D8CC681BC1D098E728AC.png) - 所有 **"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) 通过 **==显式的构造函数参数集==** 初始化对象。 ##### 直接初始化的触发语法 ![image-20240113162656352|592](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-AEF209365CD69BF8F79ECACDC8FE8B56.png) - (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) 使用**一个对象**,初始化**另一个对象**。 ##### 拷贝初始化的触发语法 ![image-20240115092144671|414](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-96E44D41F358D762CB5981C02EB3D56D.png) ##### 拷贝初始化的触发场景 - (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> ## 列表初始化的触发语法 ### 直接列表初始化 语法形式: ![image-20240113171445872|532](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-F371A8E0617C57431261B263B8821A43.png) - (1):使用 "花括号初始化列表" 初始化**具名变量** - (2):使用 "花括号初始化列表" 初始化不具名的**临时对象** - (3):在 `new-expression` 中使用 "花括号初始化列表" 初始化**动态对象** - (4):在类定义中使用 "花括号初始化列表" 为**类的非静态成员变量** 指定**类内初始化值**。 - (5):在类的构造函数的 "**成员初始化列表**" 中使用 "花括号初始化列表" 初始化类的**非静态成员变量** > [!NOTE] "花括号初始化列表" 内的内容可以为**空**、为**表达式**、或**嵌套的花括号初始化列表**。 <br> ### 拷贝列表初始化 ![image-20240113171530298|532](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-55B3ADC4C6724B494985B806FF22AE07.png) - (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` 的成员 > ![image-20240114095936211|645](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-5E01B09DE7F055EBA0D0F7EF6F051692.png) 对外只提供三个接口,**==不支持下标访问==**,**==不支持追加元素==**: - `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"。 > ![image-20240113225643132|492](_attachment/02-开发笔记/01-cpp/cpp基本概念.assets/IMG-cpp基本概念-6C2697422A051729E4528E449633E389.png) <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