⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///
WARNING \]: DETECTING HIGH ENERGY
**🌊 🌉 🌊 心手合一 · 水到渠成**

|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| **\>\>\> ACCESS TERMINAL \<\<\<** ||
| [**\[ 🦾 作者主页 \]**](https://blog.csdn.net/fengtinghuqu520?spm=1000.2115.3001.5343) | [**\[ 🔥 C语言核心 \]**](https://blog.csdn.net/fengtinghuqu520/category_12955956.html) |
| [**\[ 💾 编程百度 \]**](https://blog.csdn.net/fengtinghuqu520/category_13083835.html) | [**\[ 📡 代码仓库 \]**](https://blog.csdn.net/fengtinghuqu520/article/details/147275999?spm=1001.2014.3001.5502) |
---------------------------------------
Running Process: 100% \| Latency: 0ms
*** ** * ** ***
#### 索引与导读
* [前言](#前言)
*
* [一、C++98传统的 {} 以及 C++98的 "初始化乱象"](#一、C++98传统的 {} 以及 C++98的 “初始化乱象”)
* [二、C++11 的救赎:万物皆可 {}](#二、C++11 的救赎:万物皆可 {})
* [三、C++11 中的 std::initializer_list](#三、C++11 中的 std::initializer_list)
*
* [3.1)std::initializer_list 的便利](#3.1)std::initializer_list 的便利)
* [3.2)std::initializer_list 的接口](#3.2)std::initializer_list 的接口)
* [3.3)面试必考考点](#3.3)面试必考考点)
*
* [坑一:与普通构造函数的"神仙打架"(强烈建议记住)](#坑一:与普通构造函数的“神仙打架”(强烈建议记住))
* [坑二:千万不要返回局部 initializer_list(当前表达式的生命周期)](#坑二:千万不要返回局部 initializer_list(当前表达式的生命周期))
* [坑三:传递时按值传递即可](#坑三:传递时按值传递即可)
* [总结](#总结)
* [四、initializer_list 是否需要包含头文件?](#四、initializer_list 是否需要包含头文件?)
* [五、完整代码案例解析](#五、完整代码案例解析)
*
* [底层原理实现](#底层原理实现)
*
* [5.1)模板声明](#5.1)模板声明)
* [5.2)定义迭代器](#5.2)定义迭代器)
* [5.3)初始化列表构造函数](#5.3)初始化列表构造函数)
* [5.4)赋值运算符重载](#5.4)赋值运算符重载)
* [5.5)底层成员变量](#5.5)底层成员变量)
* [实战演示](#实战演示)
*
* [5.1)观察编译器生成的底层行为](#5.1)观察编译器生成的底层行为)
* [5.2)自定义类的底层原理](#5.2)自定义类的底层原理)
* [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议)
## 前言
> 本章重点讲解`C++11`的第一个入门内容------**列表初始化**
*** ** * ** ***
### 一、C++98传统的 {} 以及 C++98的 "初始化乱象"
`C++98`中一般**数组和结构体可以用{}进行初始化**
```cpp
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
```
*** ** * ** ***
在 `C++11` 之前,`C++` 的初始化语法可以说是五花八门,极其混乱,你需要根据不同的类型去记住不同的初始化规则:
**1. 基本类型:等号赋值或小括号**
```cpp
int a = 10;
int b(10);
```
**2. 数组:大括号**
```cpp
int arr[3] = {1, 2, 3};
```
**3. 聚合类(无构造函数的简单 struct):大括号**
```cpp
struct Point { int x, y; };
Point p = {1, 2};
```
**4. 有构造函数的类:小括号** 需要包含头文件\
```cpp
string s("hello");
```
**5. STL 容器:必须用 push_back 一次次塞入**
```cpp
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
```
这种不一致性不仅增加了学习成本,也让模板编程变得困难,因为模板内部很难猜测外部传入的类型到底该用哪种初始化语法
*** ** * ** ***
### 二、C++11 的救赎:万物皆可 {}
C++11 赋予了大括号 {} 超能力,让它成为了统一初始化的唯一指定符号。现在,你可以用 {} 来初始化几乎所有的东西:
`{}初始化`也叫**列表初始化**
无论是**内置类型、自定义类型** 都支持,并且`{}初始化`可以直接省略`=`
```cpp
// 基本类型
int a{10}; // 甚至可以省略等号
int b = {10}; // 这样也可以
// 数组
int arr[]{1, 2, 3};
// 各种对象
Point p{1, 2};
std::string s{"hello"};
// 动态分配的数组(C++98 无法直接初始化 new 出的数组元素)
int* p_arr = new int[3]{1, 2, 3};
// STL 容器(超级方便!)
std::vector v{1, 2, 3, 4, 5};
std::map m{{1, "a"}, {2, "b"}};
```
*** ** * ** ***
### 三、C++11 中的 std::initializer_list
> [🔗Lucy的空间骇客裂缝: std::initializer_list文档](https://legacy.cplusplus.com/reference/initializer_list/initializer_list/)
*** ** * ** ***
#### 3.1)std::initializer_list 的便利
一个 `vector` 对象,我想用 `N` 个值去构造初始化,那么我们得实现很多个构造函数才能支持
```cpp
std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
```
```cpp
// 假设没有 initializer_list,标准库开发者可能会疯掉:
class MyVector {
public:
MyVector(int a) { ... } // 接收 1 个参数
MyVector(int a, int b) { ... } // 接收 2 个参数
MyVector(int a, int b, int c) { ... } // 接收 3 个参数
// ... 如果用户想传 100 个参数呢?根本写不完!
};
```
*** ** * ** ***
`C++11` 库中提出了一个 `std::initializer_list` 的类,这个类的本质是底层开一个数组,将数据拷贝过来,`std::initializer_list` 内部有两个指针分别指向数组的开始和结束
* 当你写下`{1, 2, 3}`时,编译器在后台的操作如下:
* 编译器默默在栈上分配了一块连续内存,并填入数据:
`const int __hidden_array[3] = {1, 2, 3};`
* 它不把整个数组传给`vector`,而是创建一个两个指针
* **指针1 (`begin`):** 指向`数字 1` 的内存地址
* **指针2 (`end`):** 指向`数字 3` 后面的那个位置
==**`C++` 的习惯是左闭右开区间,`end` 永远指向最后一个元素的下一个位置,作为越界标志**==d
*** ** * ** ***
#### 3.2)std::initializer_list 的接口
`std::initializer_list`通用接口有三个:
* `size()`:返回列表中元素的个数。
* `begin()`:返回指向首元素的常量指针/迭代器。
* `end()`:返回指向尾后元素的常量指针/迭代器。
*** ** * ** ***
**我们看自己手搓的`MyVector`**
```cpp
#include
#include
using namespace std;
class MyVector {
private:
int* data;
size_t length;
public:
//关键点:写一个接收 std::initializer_list 的构造函数
MyVector(initializer_list list) {
//1. 使用 size() 接口:提前知道有几个元素,一次性分配好内存,避免浪费!
length = list.size();
data = new int[length];
//2. 使用 begin() 和 end() 接口:把元素一个个搬进我们的 MyVector 里
int i = 0;
for (const int* ptr = list.begin(); ptr != list.end(); ptr++) {
data[i] = *ptr;
i++;
}
cout << "成功构造了包含 " << length << " 个元素的 MyVector!\n";
}
~MyVector() {
delete[] data;
data = nullptr;
}
};
int main() {
// 完美支持大括号初始化!
MyVector v1 = { 10, 20, 30 };
MyVector v2 = { 100, 200, 300, 400, 500 };
return 0;
}
```
**关键点讲解**
* `list.begin()` 返回的是一个 `const int*`(常量指针)。为什么是常量?因为大括号里的值 `{1, 2, 3}` 是硬编码的,编译器不允许你在遍历的过程中修改它们。
* 这三个接口的设计,完美契合了 `C++` 标准模板库(`STL`)中**迭代器(`Iterator`)** 的设计哲学。这也是为什么你可以直接对 `initializer_list` 使用现代 `C++` 的 `for (int x : list)` 语法(因为范围 `for` 循环底层就是自动调用了 `begin()` 和 `end()`)
*** ** * ** ***
#### 3.3)面试必考考点
##### 坑一:与普通构造函数的"神仙打架"(强烈建议记住)
看看下面两行代码的区别:
```cpp
std::vector v1(10, 2);
std::vector v2{10, 2};
```
* `v1(10, 2)` 使用的是小括号。它调用的是普通的构造函数,意思是:"给我创建 `10` 个元素,每个元素的值都是 `2`"。(结果是:`2, 2, 2, 2...`)
* `v2{10, 2}` 使用的是大括号。**只要有大括号,编译器就会优先去匹配 `initializer_list` 构造函数** 。意思是:"给我创建一个容器,里面装入两个指定的数字:`10` 和 `2`"。(结果是:`10, 2`)
**教训** :在使用大括号初始化时,一定要清楚容器内部是不是重载了 `initializer_list` 构造函数
*** ** * ** ***
##### 坑二:千万不要返回局部 initializer_list(当前表达式的生命周期)
**`initializer_list` 只是一个指向隐藏数组的指针包裹** 。那个隐藏数组是**绑定在当前表达式的生命周期上的**
```cpp
std::initializer_list getList() {
return {1, 2, 3}; // 隐藏的数组在这里创建,函数结束后就被销毁了!
}
int main() {
auto list = getList();
// 此时 list 指向的内存已经被释放,变成了悬空指针(野指针)
// 遍历它会导致程序崩溃或未定义行为!
}
```
*** ** * ** ***
##### 坑三:传递时按值传递即可
**没必要:**
```cpp
void func(const std::initializer_list& list)
```
**正确做法:**
```cpp
void func(std::initializer_list list) { ... }
```
*** ** * ** ***
#### 总结
1. 是什么:一个包含 `begin` 和 `end` 指针的轻量级对象,用来指向一个临时的常量数组。
2. 有什么用:让自定义类型能够使用 `{1, 2, 3}` 这种直观优雅的方式进行初始化,或者用于接收不定个数的同类型函数参数。
3. 怎么用:在类的构造函数或普通函数的参数中声明 `std::initializer_list`。
4. 切记:元素是只读的;不要作为函数返回值返回;警惕 `{ }` 匹配优先级导致的出人意料的结果(参考 `vector` 那个例子)。
*** ** * ** ***
### 四、initializer_list 是否需要包含头文件?
* **通常不用包含?**
当你`#include `、`#include `、`#include