)
C11的发展目录1. 列表初始化1.1 C98传统的{}1.2 C11中的{}2. 右值引用和移动语义2.1 左值和右值2.1.1 左值lvalue /loactor value2.1.2 右值rvalue /read value2.2 左值引用和右值引用2.3 右值引用的使用2.3.1 左值引用问题2.3.2 移动构造和移动赋值2.4 右值引用和移动语义在传参中的提效2.4.1 容器新增右值引用版本接口2.4.2 万能引用与引用折叠规则2.4.2.1 万能引用2.4.2.2 引用折叠一、基础定位C11 是继 C98 后改动最大的主版本曾用代号C0x原计划 2010 年前发布最终 ISO 在 2011 年 8 月 12 日正式采纳标准号 ISO/IEC 14882:2011。 C03 到 C11 间隔 8 年是 C 史上最长版本空档自此 C 固定为每 3 年更新一个标准C14/17/20/23。二、核心新增特PPT 清单语言层面移动语义右值引用、移动构造 / 移动赋值和你自定义 string 代码对应统一初始化、auto/decltype类型推导Lambda 匿名函数、constexpr常量表达式标准库 底层多线程库 标准化内存模型正则表达式、智能指针、哈希表、std::array1.. 列表初始化1.1 C98传统的{}一、C98 大括号{}的局限C98 中{}仅能用于数组、结构体聚合初始化场景割裂、语法不统一仅数组 / 原生聚合体能用{}普通类、string、vector不能用存在窄转换隐式允许编译不报错初始化语法碎片化()、、{}混用阅读混乱无法初始化内置变量必须用。#include vector #include string struct Stu { int id; }; int main() { // 1. 数组支持{} int arr[3] {1,2,3}; // 2. 结构体聚合支持{} Stu s {1001}; // C98 不允许vector/string 不能直接用{}批量初始化 // vectorint v {1,2,3}; // 编译报错 // string str{abc}; // 编译报错 // 只能用传统括号/等号 std::vectorint v(3, 0); std::string str test; // 危险窄转换无警告 char c 1024; // int转char溢出编译器不拦截 return 0; }1.2 C11中的{}C11 引入统一初始化语法任何类型都能用 {} 初始化也叫列表初始化核心是std::initializer_list支撑容器批量赋值。 语法通用形式类型 变量{参数...}; 类型 变量 {参数...};全类型统一语法内置类型、结构体、类、STL 容器全部通用一套{}不用区分()/禁止隐式窄转换高精度→低精度数值转换int→char、double→int直接编译报错规避溢出 bug容器快速批量初始化标准库全部支持initializer_list一行完成容器填充内置类型零初始化int a{}自动初始化为 0避免随机垃圾值区分构造歧义最典型vector (数量值) vs vector {元素 1, 元素 2}vectorint v(3, 5)3 个 5vectorint v{3, 5}元素 3、5#define _CRT_SECURE_NO_WARNINGS 1 #include iostream #include vector #include map using namespace std; #include assert.h struct Point { int _x; int _y; }; class Date { public: Date(int year 1, int month 1, int day 1) :_year(year) , _month(month) , _day(day) { cout Date(int year, int month, int day) endl; } Date(const Date d) :_year(d._year) , _month(d._month) , _day(d._day) { cout Date(const Date d) endl; } private: int _year; int _month; int _day; }; int main() { // C98支持的 int a1[] { 1, 2, 3, 4, 5 }; int a2[5] { 0 }; Point p { 1, 2 }; // C11支持的 // 内置类型支持 int x0 1; int x1 { 2 }; int x2{ 2 }; // 自定义类型支持 Date d0(2025, 5, 19); // 构造拷贝构造-优化直接构造 Date d1 { 2025, 5, 19 }; Date d3 { 2025, 5, 19 }; //Date d2 2025; Date d2 { 2025 }; vectorDate v; // 使用场景 v.push_back(2025); v.push_back({ 2025, 5, 19 }); // 这里d2引用的是{ 2024, 7, 25 }构造的临时对象 const Date d4 { 2024, 7, 25 }; return 0; }2. 右值引用和移动语义C98 的 C 语法里已经存在引用语法C11 新增了右值引用这一语法特性C11 发布之后我们之前学习的引用就被称作左值引用。不论是左值引用还是右值引用本质都是为对象取别名。2.1 左值和右值2.1.1 左值lvalue /loactor value左值是代表数据的表达式变量名、解引用指针等具备持久内存状态可以获取地址 既可放在赋值符号左侧也可放在右侧 加const修饰的左值禁止赋值但依旧能取地址。核心特征内存常驻拥有合法内存地址支持取地址赋值双向通用a 10、int b a都合法const 左值可读、可取地址不可修改。#include iostream using namespace std; int main() { int a 10; // a是普通左值 int* p a; *p 20; // *p 解引用指针属于左值 // 左值可放赋值左右两侧 a 30; int b a; // const修饰左值可取地址不能赋值 const int c 100; cout c endl; // 合法取地址 // c 200; // 编译报错const左值禁止修改 return 0; }2.1.2 右值rvalue /read value右值是代表数据的表达式分为两类字面常量、表达式生成的临时对象仅能出现在赋值符号右侧不能放在左侧无法取地址。核心特征无持久内存多存于寄存器 / 临时栈不支持取地址只能读取数值不能被赋值包含纯右值字面量、运算临时结果、C11 新增将亡值std::move转换后的临时对象。#include string using namespace std; string getTemp() { return 临时字符串; // 返回匿名临时对象右值 } int main() { int a 10; // 1. 字面量是右值 a 100; // 100 a; 报错右值不能放在赋值左边 // 100; 报错右值无法取地址 // 2. 算术表达式结果为右值 int b a 20; // (a 20); 报错 // 3. 函数返回临时对象是右值 string s getTemp(); // getTemp(); 报错 return 0; }2.2 左值引用和右值引用Type r1 x; Type rr1 y; 第⼀个语句就是左值引用左值引用就是给左值取别名第二个就是右值引用同样的道理右值引用就是给右值取别名。Type r1 x; // 左值引用给左值取别名 Type rr1 y; // 右值引用给右值取别名左 / 右值引用绑定规则1. 普通左值引用Type只能绑定左值不能直接绑定右值int b 1; int r1 b; // 合法绑定左值b int err 10; // 编译报错10是右值普通左值引用不能绑右值2. const 左值引用const Type万能绑定左值、右值都可以绑定const int rx1 10; // 绑定字面量右值 const double rx2 x y; // 绑定运算临时右值 const string rx4 string(111); // 绑定临时对象右值3. 右值引用Type只能绑定纯右值 /move 转换后的将亡值不能直接绑定普通左值// 合法绑定纯右值 int rr1 10; double rr2 x y; string rr4 string(111); // 报错直接绑定普通左值 int err b; // 合法std::move把左值转为将亡值再绑定右值引用 int rrx1 move(b); string rrx4 move(s);4. 变量表达式都是左值属性右值被右值引用绑定后该右值引用变量是左值。int rr1 10; // rr1 可以取地址rr1自身是左值 cout rr1 endl; int r6 rr1; // 合法左值引用绑定左值rr1 // int rrx6 rr1; // 报错rr1是左值不能直接绑右值引用 int rrx6 move(rr1);// 必须再次move转为右值才能绑定5. std::move 详解template class _Ty remove_reference_t_Ty move(_Ty _Arg) { return static_castremove_reference_t_Ty(_Arg); }作用说明:move 是标准库函数模板核心逻辑是强制类型转换remove_reference_t剥离变量原本的引用修饰再强转为对应类型的右值引用功能接收任意左值转换为右值从而可以被右值引用绑定触发移动语义int b 1; int rrx1 move(b); // move(b) 将左值b转为将亡右值 string s(111); string rrx4 move(s);示例代码#includeiostream #includestring #includecmath using namespace std; int main() { // 1. 定义各类左值可取地址 int* p new int(0); int b 1; const int c b; *p 10; string s(111111); s[0] x; double x 1.1, y 2.2; // 左值引用绑定各类左值 int r1 b; int* r2 p; int r3 *p; string r4 s; char r5 s[0]; // 右值引用绑定纯右值 int rr1 10; double rr2 x y; double rr3 fmin(x, y); string rr4 string(111111); // const左值引用万能绑定 const int rx1 10; const double rx2 x y; const double rx3 fmin(x, y); const string rx4 string(111111); // move转换左值绑定右值引用 int rrx1 move(b); int* rrx2 move(p); int rrx3 move(*p); string rrx4 move(s); string rrx5 (string)s; // 等价move强制转换 // 验证所有带名字变量都是左值可以取地址 cout b endl; cout r1 endl; cout rr1 endl; // 右值引用变量rr1是左值直接绑定报错需要再次move int r6 rr1; // int rrx6 rr1; // 编译错误 int rrx6 move(rr1); delete p; return 0; }2.3 右值引用的使用2.3.1 左值引用问题1. 无法用左值引用返回局部对象函数内的局部变量在函数执行结束后会被析构销毁。同理vectorvectorint generate(...)返回局部vv也是非法野引用。2. C98 的妥协方案输出型参数既然不能引用返回局部对象只能额外传一个外部容器作为输出参数函数内部直接填充外部对象规避拷贝 野引用问题。 以generate举例改造C98这里的传值返回拷贝代价就太大了C11之后效率就很高不用担心效率 vectorvectorint generate(int numRows) { vectorvectorint vv(numRows); for (int i 0; i numRows; i) { vv[i].resize(i 1, 1); } for (int i 2; i numRows; i) { for (int j 1; j i; j) { vv[i][j] vv[i - 1][j] vv[i - 1][j - 1]; } } return vv; } C11之前得通过输出型参数改善效率 void generate(int numRows, vectorvectorint vv) { //vectorvectorint vv(numRows); vv.resize(numRows); for (int i 0; i numRows; i) { vv[i].resize(i 1, 1); } for (int i 2; i numRows; i) { for (int j 1; j i; j) { vv[i][j] vv[i - 1][j] vv[i - 1][j - 1]; } } }2.3.2 移动构造和移动赋值一、拷贝构造的痛点1. 拷贝构造Type(const Type x)参数是const 左值引用逻辑完整深拷贝堆资源分配一块全新堆内存把源对象所有数据逐字节复制两个对象各自持有独立堆内存析构时各自释放。2. 存在巨大性能缺陷对于string/vector这类存储堆内存的容器 函数返回局部对象时会连续多次深拷贝字符串越长、容器越大内存分配 / 拷贝 / 释放开销越高PPT 中addStrings函数 C98 场景会两次深拷贝、两次析构释放堆内存。3. 移动语义诞生目的临时对象用完立刻销毁没必要完整拷贝它的堆资源直接窃取临时对象内部的堆指针仅交换几个内置成员变量无堆内存操作O (1) 常数开销。二、移动构造函数Type(Type x)本质是构造函数和拷贝构造Type(const Type x)构成重载第一个参数必须是本类类型的右值引用Type若存在额外参数其余参数必须给默认值。string 移动构造源码解析// 移动构造 string(string s) { cout string(string s) -- 移动构造 endl; swap(s); // 交换this与s内部的_str/_size/_capacity } void swap(string s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); }执行逻辑新对象初始_str是空指针和传入的临时对象s交换三个成员堆指针、有效长度、容量资源完成转移新对象拿到堆内存原临时对象s内部指针变为空原临时对象析构时delete[]空指针无任何内存释放开销。触发场景仅右值会调用函数值返回局部对象addStrings中return str;局部str是将亡值std::move(左值)转换后的对象匿名临时对象bit::string(123)。有无移动构造的运行对比场景 1类只有拷贝构造执行string ret bit::addStrings(11111,222222222);函数内部局部str创建return str触发拷贝构造分配新堆内存复制字符串函数局部str析构释放原有堆内存外部ret持有拷贝出来的堆内存日志打印两次拷贝构造、两次析构两次堆内存分配 释放。#include iostream #include cstring // --------------------- 版本1只有拷贝构造无移动构造 --------------------- class MyStringCopyOnly { private: char* _str; size_t _size; public: // 构造函数 MyStringCopyOnly(const char* s) { _size std::strlen(s); _str new char[_size 1]; std::strcpy(_str, s); std::cout [构造] 堆内存分配: (void*)_str \n; } // 拷贝构造函数深拷贝 MyStringCopyOnly(const MyStringCopyOnly other) { _size other._size; _str new char[_size 1]; std::strcpy(_str, other._str); std::cout [拷贝构造] 新堆内存分配: (void*)_str \n; } // 拷贝赋值 MyStringCopyOnly operator(const MyStringCopyOnly other) { if(this other) return *this; delete[] _str; _size other._size; _str new char[_size 1]; std::strcpy(_str, other._str); std::cout [拷贝赋值] 新堆内存分配: (void*)_str \n; return *this; } // 析构 ~MyStringCopyOnly() { std::cout [析构] 释放堆内存: (void*)_str \n; delete[] _str; } // 禁止移动构造、移动赋值 MyStringCopyOnly(MyStringCopyOnly) delete; MyStringCopyOnly operator(MyStringCopyOnly) delete; };日志 场景1只有拷贝构造无移动构造 [构造] 堆内存分配: 0x55f87c2b22a0 [拷贝构造] 新堆内存分配: 0x55f87c2b22d0 [析构] 释放堆内存: 0x55f87c2b22a0 [析构] 释放堆内存: 0x55f87c2b22d0场景 2类实现移动构造同样代码return str触发移动构造仅交换_str/_size/_capacity三个指针 / 数字不新分配堆局部str析构时内部_str已为空无需释放堆日志打印两次移动构造无多余堆内存操作性能大幅提升。// --------------------- 版本2带移动构造函数 --------------------- class MyStringMoveAble { private: char* _str; size_t _size; public: // 构造函数 MyStringMoveAble(const char* s) { _size std::strlen(s); _str new char[_size 1]; std::strcpy(_str, s); std::cout [构造] 堆内存分配: (void*)_str \n; } // 拷贝构造 MyStringMoveAble(const MyStringMoveAble other) { _size other._size; _str new char[_size 1]; std::strcpy(_str, other._str); std::cout [拷贝构造] 新堆内存分配: (void*)_str \n; } // 移动构造核心资源转移不分配内存 MyStringMoveAble(MyStringMoveAble other) noexcept { // 直接偷对方的资源 _str other._str; _size other._size; // 置空源对象源对象析构不会释放这块堆内存 other._str nullptr; other._size 0; std::cout [移动构造] 仅转移指针, 无新堆分配! 接管内存: (void*)_str \n; } // 拷贝赋值 MyStringMoveAble operator(const MyStringMoveAble other) { if(this other) return *this; delete[] _str; _size other._size; _str new char[_size 1]; std::strcpy(_str, other._str); std::cout [拷贝赋值] 新堆内存分配: (void*)_str \n; return *this; } // 移动赋值 MyStringMoveAble operator(MyStringMoveAble other) noexcept { if(this other) return *this; delete[] _str; _str other._str; _size other._size; other._str nullptr; other._size 0; std::cout [移动赋值] 仅转移指针无新堆分配\n; return *this; } // 析构 ~MyStringMoveAble() { if(_str ! nullptr) { std::cout [析构] 释放堆内存: (void*)_str \n; delete[] _str; } else { std::cout [析构] 对象资源已被移走, 无需释放堆内存\n; } } }; 场景2实现移动构造函数 [构造] 堆内存分配: 0x55f088e282a0 [移动构造] 仅转移指针, 无新堆分配! 接管内存:0x55f088e282a0 [析构] 对象资源已被移走, 无需释放堆内存 [析构] 释放堆内存: 0x55f088e282a02.4 右值引用和移动语义在传参中的提效2.4.1 容器新增右值引用版本接口// C98接收左值只能拷贝 void push_back (const value_type val); // C11接收右值可以移动 void push_back (value_type val);传入左值对象lt.push_back(s1)调用const T版本重载触发拷贝构造深拷贝堆内存开销大。传入右值对象std::move(s1)、临时对象lt.push_back(move(s1))调用T版本重载如果你的类实现了移动构造函数资源所有权转移只交换指针不开辟新堆内存性能高如果没有实现移动构造右值引用版本只能降级调用拷贝构造依然深拷贝移动优化失效。2.4.2 万能引用与引用折叠规则2.4.2.1 万能引用万能引用唯一形式函数模板中T并且T需要由实参推导得出。templateclass T void Function(T t); // 万能引用传左值进去 → 通过引用折叠实例化出左值引用版本void Function(int t)传右值进去 → 实例化出右值引用版本void Function(int t)2.4.2.2 引用折叠规则两个引用叠加只要有一个左值引用 ()折叠结果就一定是左值引用示例templateclass T void f1(T x); // T永远左值引用不能接收右值0 templateclass T void f2(T x);//万能引用左值→左值引用右值→右值引用