C++进阶:C++11相关
◆博主名称少司府欢迎来到少司府的博客☆*: .. o(≧▽≦)o ..:*☆⭐数据结构系列个人专栏初阶数据结构_少司府的博客-CSDN博客⭐C基础个人专栏C初阶_少司府的博客-CSDN博客⭐琢玉成器终有时笔底生花夺锦归目录1. 列表初始化1.1 C98中的{}1.2 C11中的{}1.3 C11中的initializer_list2.右值引用和移动语义2.1 右值和左值2.1.1 左值2.1.2 右值2.2 左值引用和右值引用2.3 延长生命周期2.4 左值和右值的参数匹配2.5 右值引用和移动语义的使用场景2.5.1 左值引用使用场景2.5.2 右值引用和移动语义使用场景3. 类型分类4. 引用折叠和完美转发4.1 引用折叠4.2 完美转发1. 列表初始化1.1 C98中的{}在C98中利用{}初始化的情况有两种结构体的初始化和数组的初始化。struct Point { int _x; int _y; }; int main() { int arr[]{1,2,3,4,5}; Point p{1,2}; return 0; }1.2 C11中的{}1、C11以后试图实现一切对象皆可用{}初始化{}初始化也叫做列表初始化。2、内置类型和自定义类型都可以列表初始化自定义类型列表初始化本质就是类型转换。// C11⽀持的 // 内置类型⽀持 int x1 { 2 }; // ⾃定义类型⽀持 // 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象 // 临时对象再去拷⻉构造d1编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化d1 Date d1 { 2025, 1, 1 };3、列表初始化const引用的临时对象。// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象 ,临时对象具有常性 const Date d2 { 2024, 7, 25 };4、需要注意的是C98⽀持单参数类型转换时也可以不⽤{}Date d3 { 2025 }; // C11 Date d4 2025; // C985、在列表初始化的时候可以省略// 可以省略掉 Point p1{ 1, 2 }; int x2{ 2 }; Date d6{ 2024, 7, 25 }; const Date d7{ 2024, 7, 25 }; // 不⽀持只有{}初始化才能省略 // Date d8 2025;6、在多参数类型构造对象的时候列表初始化会方便很多。vectorDate v; v.push_back(d1); v.push_back(Date(2025, 1, 1)); // ⽐起有名对象和匿名对象传参这⾥{}更有性价⽐ v.push_back({ 2025, 1, 1 });1.3 C11中的initializer_listC11提供一个类模板initializer_list这是它的文档initializer_list - C Reference有了initializer_list我们可以实现n个值去初始化构造一个容器对象// 构造拷贝构造优化 vectorint v1 { 1,2,3,4 }; vectorint v2{ 1,2,3,4 }; // 构造两者的构造都是隐式类型转换为initializer_listint类型 vectorint v3({ 1,2,3,4,5 });initializer_list特点{ }内括起来的数据类型是一样的。2.右值引用和移动语义C11之前学习的都是左值引用C11之后增加了右值引用的概念两者本质都是给对象取别名。2.1 右值和左值2.1.1 左值左值是一个表示数据的表达式一般具有持久状态可以取地址可以出现在赋值符号的左右两边const修饰的左值不能更改它的数据但是可以取地址。// 左值可以取地址 // 以下的p、b、c、*p、s、s[0]就是常⻅的左值 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;如图常见的左值一般包括变量名、计算表达式*p、p等、函数调用表达式s[ 0 ]返回类型是引用Ref可以修改。2.1.2 右值右值也是一个表示数据的表达式一般不具有持久状态不能取地址只能出现在赋值符号的右边。// 以下⼏个10、x y、fmin(x, y)、string(11111)都是常⻅的右值 10; x y; fmin(x, y); string(11111);如图常见的右值包括字面量常量、表达式返回的临时对象、匿名对象、函数的非引用返回等。2.2 左值引用和右值引用1、左值引用来引用左值右值引用来引用右值// 左值引⽤给左值取别名 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(11111);2、左值引用不能直接引用左值但是const左值引用可以引用右值const int rx1 10;3、右值引用不能直接引用左值但是右值引用可以引用move(左值)move的本质就是类型转换左值表达式变成将亡值。move(b); // 不改变b本身的状态 int rrx1 move(b); // move是一个函数模板4、注意变量表达式本身是左值的属性也就是说一个右值引用的变量名本身是左值属性的// 这⾥要注意的是rr1的属性是左值所以不能再被右值引⽤绑定除⾮move⼀下 int r6 rr1; // 普通左值引用 int 只允许绑定左值;rr1 是具名变量表达式rr1属于左值匹配绑定规则编译通过。 // int rrx6 rr1; int rrx6 move(rr1);5、语法上左值引用和右值引用都是取别名不开空间汇编上两者都是指针实现的指令都是一样的。2.3 延长生命周期右值引用和const左值引用都可以延长临时对象的生命周期但是const引用的对象不能被修改。std::string s1 Test; // std::string r1 s1; // 错误不能绑定到左值 const std::string r2 s1 s1; // OK到 const 的左值引⽤延⻓⽣存期 // r2 Test; // 错误不能通过到 const 的引⽤修改 std::string r3 s1 s1; // OK右值引⽤延⻓⽣存期 r3 Test; // OK能通过到⾮ const 的引⽤修改 std::cout r3 \n;2.4 左值和右值的参数匹配1、C98中我们实现一个const左值引用作为参数的函数那么左值右值都可以匹配2、C11之后我们要重载实现对应匹配的函数void f(int x) { std::cout 左值引⽤重载 f( x )\n; } void f(const int x) { std::cout const 的左值引⽤重载 f( x )\n; } void f(int x) { std::cout 右值引⽤重载 f( x )\n; }2.5 右值引用和移动语义的使用场景先给出以下代码class Solution { public: // 传值返回需要拷⻉ string addStrings(string num1, string num2) { string str; int end1 num1.size() - 1, end2 num2.size() - 1; // 进位 int next 0; while (end1 0 || end2 0) { int val1 end1 0 ? num1[end1--] - 0 : 0; int val2 end2 0 ? num2[end2--] - 0 : 0; int ret val1 val2 next; next ret / 10; ret ret % 10; str (0 ret); } if (next 1) str 1; reverse(str.begin(), str.end()); return str; } // 这⾥的传值返回拷⻉代价就太⼤了 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; } };当前两个函数是实现字符串相加和杨辉三角的逻辑。2.5.1 左值引用使用场景回顾一下左值引用主要使⽤场景是——在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉同时还可以修改实参和修改返回对象的价值。但是如果遇到这个情况——要返回的对象的作用域只在当前函数栈帧出了作用域就销毁了就不能直接返回左值引用。如上述两个函数。这里就需要用到右值引用来解决这个问题。在C11之前该问题可以靠输出型参数解决void addStrings(const string num1, const string num2, string res) { res.clear(); // 清空原有内容复用内存 int i num1.size() - 1; int j num2.size() - 1; int carry 0; while (i 0 || j 0) { int v1 i 0 ? num1[i--] - 0 : 0; int v2 j 0 ? num2[j--] - 0 : 0; int sum v1 v2 carry; carry sum / 10; res.push_back(sum % 10 0); } if (carry ! 0) { res.push_back(1); } reverse(res.begin(), res.end()); }如图要返回的res需要手动传入。这时候可能有疑惑既然出了当前栈帧就会销毁那可以在堆上开辟空间new一个res吗绝对不可以调用者不知道返回的对象是堆上开辟的没有手动释放的话会引发内存泄漏2.5.2 右值引用和移动语义使用场景右值引用使用场景局部对象出了当前函数就销毁了不能传左值引用只能传右值引用。string s6 Solution().addStrings(11111, 22222); // 右值引用延长临时对象的生命周期,两个参数可以通过移动构造减少拷贝构造带来的消耗 cout s6.c_str() endl;移动构造和移动赋值// 移动构造 string(string s) { cout string(string s) -- 移动构造 endl; swap(s); } // 移动赋值 string operator(string s) { cout string operator(string s) -- 移动赋值 endl; swap(s); return *this; }两者都是通过swap交换将亡值的资源来实现构造大大减少了拷贝带来的消耗。rmove::string s5 move(s1); // 触发移动构造move之后移动构造导致s1的资源被释放 //cout s1.c_str() endl; error3. 类型分类1、C11以后进一步对类型进行了划分右值被分为纯右值(pure value简称prvalue)和将亡值(expiring value简称xvalue)。2、纯右值指的是那些字面量常量或求值结果相当于字面值或是一个不具名的临时对象。例如42、true、nullptr或者类似str.substr(1,2)、str1 str2传值返回函数调用或者整型a、baab等。3、将亡值指的是返回右值引用的函数的调用表达式和转换为右值引用的转换函数的表达式。例如move(x)、static_castX(x)4、泛左值(generalized value简称glvaue)包括将亡值和左值4. 引用折叠和完美转发4.1 引用折叠1、C中不能直接定义引用的引用但可以通过模板或者typedef中的类型操作可以构成引用的引用。int r i; // error,直接报错2、在构成引用的引用的时候C给出了一个引用折叠的规则右值引用的右值引用折叠成右值引用其他都折叠成左值引用。typedef int lref; typedef int rref; lref r1 n; // r1 的类型是 int lref r2 n; // r2 的类型是 int rref r3 n; // r3 的类型是 int rref r4 1; // r4 的类型是 int当然我们也可以通过模板来观察templateclass T void Function(T t) { int a 0; T x a; //x; cout a endl; cout x endl endl; } void test_04() { // 10是右值推导出T为int模板实例化为void Function(int t) Function(10); // 右值 int a; // a是左值推导出T为int引⽤折叠模板实例化为void Function(int t) Function(a); // 左值 // std::move(a)是右值推导出T为int模板实例化为void Function(int t) Function(std::move(a)); // 右值 const int b 8; // a是左值推导出T为const int引⽤折叠模板实例化为void Function(const int t) // 所以Function内部会编译报错x不能 Function(b); // const 左值 // std::move(b)右值推导出T为const int模板实例化为void Function(const int t) // 所以Function内部会编译报错x不能 Function(std::move(b)); // const 右值 }4.2 完美转发templateclass T class list { typedef list_nodeT Node; public: typedef list_iteratorT, T, T* iterator; typedef list_iteratorT, const T, const T* const_iterator; // 万能引用 templateclass X void push_back(X x) { insert(end(), forwardX(x)); } };如图完美转发forward可以保留x原本的类型。即如果没有forward若引用折叠之后X类型是右值引用x是某个右值的引用但是x本身(变量名)是左值直接传仍然为左值forward则可以保留x右值引用的属性。