【C++ 11】Cyber骇客 最后的一片净土 ——【列表初始化{}】(附带完整代码解析)

⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///


WARNING \]: DETECTING HIGH ENERGY **🌊 🌉 🌊 心手合一 · 水到渠成** ![分隔符](https://i-blog.csdnimg.cn/direct/60a3de2294e9439abad47378e657b337.gif) |------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| | **\>\>\> 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 ` 或 `#include ` 时,这些标准库头文件的内部已经自动帮你 `#include ` 了 * **什么时候必须手动包含?** 当我们自己实现一个类(比如你之前写的 `MyVector`),并且这个类没有引用任何像 `vector`、`string`、`iostream` 这样的大型标准库头文件 或者只想单纯地使用 `std::initializer_list` 作为一个函数的参数,而不打算用任何其他的 `STL 容器` *** ** * ** *** ### 五、完整代码案例解析 ```cpp #include #include #include #include #include // 手写构造函数时建议显式包含 using namespace std; // --- 第一部分:底层原理模拟 (以自定义 MiniVector 为例) --- template class MiniVector { public: typedef T* iterator; // 1. 构造函数:支持大括号初始化 {1, 2, 3} MiniVector(initializer_list l) { cout << "调用了 initializer_list 构造函数, 元素个数: " << l.size() << endl; // 实际开发中这里会分配内存并拷贝数据,这里简略演示逻辑 for (auto e : l) { push_back(e); // 模拟向底层空间压入数据 } } // 2. 赋值运算符重载:支持 v1 = {10, 20} MiniVector& operator=(initializer_list l) { cout << "调用了 operator= 赋值重载" << endl; return *this; } private: iterator _start = nullptr; iterator _finish = nullptr; iterator _endofstorage = nullptr; }; // --- 第二部分:实战演示 --- int main() { // 1. 探究 initializer_list 本身 initializer_list mylist = { 10, 20, 30 }; // sizeof 通常是 2 个指针的大小(起始和结束地址) cout << "mylist 的对象大小 (sizeof): " << sizeof(mylist) << " bytes" << endl; int stackVar = 0; cout << "mylist.begin() 地址: " << mylist.begin() << endl; cout << "mylist.end() 地址: " << mylist.end() << endl; cout << "局部变量栈地址: " << &stackVar << endl; cout << "---" << endl; // 2. 几种初始化写法的微妙差别 (使用标准库 vector) // 写法 A: 显式传参构造 vector v1({ 1, 2, 3, 4, 5 }); // 写法 B: 拷贝初始化(最常用),编译器会优化成直接构造,效率极高 vector v2 = { 1, 2, 3, 4, 5 }; // 写法 C: 常量引用绑定临时对象(会延长临时对象的生命周期) const vector& v3 = { 1, 2, 3, 4, 5 }; // 3. 复杂容器的应用:map 的嵌套初始化 // 外层是 initializer_list,内层每个 {} 被识别为 pair map dict = { {"sort", "排序"}, {"string", "字符串"} }; cout << "map 元素个数: " << dict.size() << endl; // 4. 赋值支持演示 v1 = { 10, 20, 30, 40, 50 }; cout << "v1 重新赋值后的 size: " << v1.size() << endl; // 5. 自定义类演示 MiniVector myV = { 6, 7, 8 }; myV = { 9, 10 }; return 0; } ``` *** ** * ** *** #### 底层原理实现 ##### 5.1)模板声明 ```cpp template class MiniVector { ``` * **意义**:类型通用化。 * 如果不写模板,你只能造一个 `IntVector`。有了 `T`,这个容器就能装 `int`、`double` 甚至你自定义的 `Student` 类。它让 `initializer_list` 能够自适应各种数据类型。 *** ** * ** *** ##### 5.2)定义迭代器 ```cpp typedef T* iterator; ``` 在 `vector` 这种内存连续的容器里,指针就是天然的迭代器 给 `T*` 取名`iterator`是为了向标准库看齐,让外部用户可以用 `MiniVector::iterator it` 这种标准写法,而不需要关心底层到底是不是指针 *** ** * ** *** ##### 5.3)初始化列表构造函数 ```cpp MiniVector(initializer_list l) ``` **核心逻辑:** 当你写 `MiniVector v = {1, 2, 3};` 时,编译器会把 `{1, 2, 3}` 包装成 `l` 传进来 **内部循环的意义:** `initializer_list` 本身不存储数据,它只是数据的"搬运工"。**通过 `for (auto e : 1)`,我们遍历这个临时区域,并调用 `push_back` 把数据深度拷贝到 `MiniVector` 自己管理的堆内存中** *** ** * ** *** ##### 5.4)赋值运算符重载 ```cpp MiniVector& operator=(initializer_list l) ``` **场景:** 当你已经有了一个`v`对象,你想给它换一批新数据:`v = {10, 20};` **关键点:** 如果没有这个重载,编译器会尝试寻找其他的赋值路径(**很容易造成内存浪费的情况**) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d48d3f8f2efa429fbd5c9daf83d2997e.png) *** ** * ** *** ##### 5.5)底层成员变量 ```cpp private: iterator _start = nullptr; iterator _finish = nullptr; iterator _endofstorage = nullptr; ``` **初始化为 `nullptr`:** 防止野指针,确保在一个空容器被创建时,它是安全的 *** ** * ** *** #### 实战演示 ##### 5.1)观察编译器生成的底层行为 **╔═█▓▒░ CODE CORE 🔥** **┌─────────────┐ │ 代码关键点 │`initializer_list mylist = { 10, 20, 30 };` └─────────────┘** 当你写下 `{ 10, 20, 30 }` 时,**编译器会先在内存(通常是当前函数的栈区)开辟一块连续的、只读的隐藏数组** 你可以理解为编译器在底层默默生成了这样一行代码: ```cpp // 这是一个隐藏的、只读的临时数组 const int __tmp_array[3] = { 10, 20, 30 }; ``` **当你执行下面这行代码:** ```cpp cout << "mylist 的对象大小 (sizeof): " << sizeof(mylist) << " bytes" << endl; ``` 你会发现它的大小固定(如 16 字节),证明了它**不存数据**,只存指针 无论你写 3 个数还是 300 个数,sizeof 都不变 *** ** * ** *** **生命周期的绑定** 这一步非常关键!编译器规定:**隐藏数组的生命周期与 `mylist` 对象同步。** * 只要 `mylist` 对象还活着,那个隐藏的数组 `{10, 20, 30}` 就保证存在。 * 一旦 `mylist` 离开了它的作用域(比如函数结束了),隐藏数组也会随之销毁。 这就是为什么不能返回 `initializer_list` 的原因:如果你把 `mylist` 返回出去,函数结束了,虽然**指针** 还在,但**隐藏数组**已经被回收了,你拿到的就是一堆乱码 *** ** * ** *** ##### 5.2)自定义类的底层原理 ```cpp MiniVector myV = { 6, 7, 8 }; ``` **你自己写了三个指针,然后你打印它发现是三个指针的大小** ; 你自己 `new`了内存,然后你打印地址发现是在堆上 *** ** * ** *** ## 💻结尾--- 核心连接协议 **警告:** 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠 *** ** * ** *** **【📡】 建立深度链接:** **关注**本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。 **【⚡】 能量过载分发:** 执行**点赞**操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。 **【💾】 离线缓存核心:** 将本页加入**收藏**。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。 **【💬】 协议加密解密:** 在**评论区**留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。 **【🛰️】 信号频率投票:** 通过**投票**发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。 *** ** * ** *** ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57b03915c54b43a7a03fa92dbbfe57c3.gif) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/0905dc972de8414bb602715de3f866ee.gif)

相关推荐
watson_pillow11 小时前
c++ 协程的初步理解
开发语言·c++
故事和你9111 小时前
洛谷-算法1-2-排序2
开发语言·数据结构·c++·算法·动态规划·图论
Tanecious.13 小时前
蓝桥杯备赛:Day6-B-小紫的劣势博弈 (牛客周赛 Round 85)
c++·蓝桥杯
流云鹤13 小时前
Codeforces Round 1090 (Div. 4)
c++·算法
小菜鸡桃蛋狗13 小时前
C++——string(上)
开发语言·c++
wljy113 小时前
第十三届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(个人见解,已完结)
c语言·c++·算法·蓝桥杯·stl
清空mega14 小时前
C++中关于数学的一些语法回忆(2)
开发语言·c++·算法
想唱rap14 小时前
线程池以及读写问题
服务器·数据库·c++·mysql·ubuntu
望眼欲穿的程序猿15 小时前
Vscode Clangd 无法索引 C++17 或者以上标准
java·c++·vscode