FEATURED · 精选文章

C++11新特性

发布时间 / 2026/9/4 22:13:26
来源 / 创域科博编辑部
栏目 / 资讯中心
C++11新特性 一列表初始化1.1c98传统的{}c98中一般数组和结构体可以用{}进行初始化。#includeiostream using namespace std; struct Point { int _x; int _y; }; int main() { char array1[] { a,b,c,d,e,f,g }; int array2[8] { 0 }; Point p { 3,4 }; return 0; }1.2c11中的{}c11以后想统一初始化方式试图实现一切对象皆可用{}初始化{}初始化也叫做列表初始化。内置类型支持自定义类型也支持自定义类型本质是类型转换中间会产生临时对象最后优化了以后变成直接构造。{}初始化的过程中可以省略掉 。c11列表初始化的本意是想实现一个大统一的初始化方式其次它在有些场景下带来不少便利如容器push / insert多参数构造的对象时{}初始化会很方便。#includeiostream #includevector using namespace std; 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 1, int month 1, int day 1) endl; } Date(const Date d) :_year(d._year) , _month(d._month) , _day(d._day) { cout Date(const Date d) endl; } Date(Date d) :_year(d._year) , _month(d._month) , _day(d._day) { cout Date(Date d) endl; } private: int _year; int _month; int _day; }; int main() { //c98支持的 char array1[] { a,b,c,d,e,f,g }; int array2[8] { 0 }; Point p { 3,4 }; //c11支持的 //内置类型支持 int x1 { 2 }; //自定义类型支持 // 这里本质是用{2026,8,19}构造一个Date临时对象临时对象再去拷 //贝构造d1编译器优化后合二为一变成{2026,8,19}直接构造初始化。 Date d1 { 2026,8,19 };//调用构造 //这里d2引用的是{2026,4,13}构造临时对象。 const Date d2 { 2026,4,13 }; //c98以后支持单参数时类型转换也可以不用{} Date d3 { 2026 }; Date d4 2026; //可以省略掉 Point p1{ 4,9 }; int x2{ 13 }; Date d5{ 2026,1,5 }; const Date d6{ 2026,6,6 }; //只有大括号初始化才能省略掉 //Date d7 2026; X vectorDate v; v.push_back(d1); //有名对象传参 v.push_back(Date(2025, 1, 1)); //匿名对象传参 //使用{}初始化更方便 v.push_back({ 2024,3,17 }); return 0; }1.3c11中的std::initializer_list1上面的初始化已经很方便但是对象容器的初始化还是不太方便比如一个vector对象要用N个值去构造初始化那么要实现多个构造函数才能支持。2c11库中提出了一个std::initializer_list的类auto i1 { 10, 20 , 30 }// the type of i1 is an initializer_list 这个类的本质是底层开一个数组将数据拷贝过来std::initializer_list内部有两个指针分别指向数组的开始和结束。3有了std::initializer_list就可以像下面这样初始化对象容器了。#includeiostream #includevector #includestring using namespace std; int main() { initializer_listint i1{ 1,2,3,4,5 }; initializer_listint::iterator it1 i1.begin(); while (it1 ! i1.end()) { cout *it1 ; it1; }//1 2 3 4 5 //有了initializer_list就可以这样初始化了。 vectorstring v{ {insert},{sort},{erase},{empty},{hello } }; for (auto ev : v)//insert sort erase empty hello cout ev ; return 0; }二右值引用和移动语义c98的c语法中就有引用的语法而c11中新增了右值引用语法特性我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用都是给对象取别名。2.1左值和右值1左值是一个表示数据的表达式 (如变量名或解引用的指针)一般是有持久状态存储在内存中我们可以获取它的地址左值可以出现赋值符号的左边也可以出现在赋值符号右边。定义时 const 修饰符后的左值不能给他赋值但是可以取它的地址。2右值也是一个表示数据的表达式要么是字面值常量、要么是表达式求值过程中创建的临时对象等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。3值得一提的是左值的英文简写为 lvalue右值的英文简写为 rvalue。传统认为它们分别是 left value、right value 的缩写。现代 C 中lvalue 被解释为 loactor value 的缩写可意为存储在内存中、有明确存储地址可以取地址的对象而 rvalue 被解释为 read value指的是那些可以提供数据值但是不可以寻址例如临时变量字面量常量存储于寄存器中的变量等也就是说左值和右值的核心区别就是能否取地址。#include iostream using namespace std; int main() { //左值可以取地址 int x 4; int* p1 x; int** pp1 p1; int x1 x; const int a 10; const int* pa1 a; pa1 p1;//权限变小。 string s1 (float); s1[0] d; //右值不能取地址 int num1 3; int num2 4; //常见的右值 4, fmin(num1, num2); string(double);//匿名对象 //右值取地址都会报错 //cout 4 endl; //cout fmin(num1, num2) endl; //cout string(double) endl; //cout (num1 num2) endl; return 0; }2.2左值引用和右值引用1Type r1 x; Type rr1 y; 第一个语句就是左值引用左值引用就是给左值取别名第二个就是右值引用同样的道理右值引用就是给右值取别名。2左值引用不能直接引用右值但是 const 左值引用可以引用右值。10是一个右值const int x 103右值引用不能直接引用左值但是右值引用可以引用 move (左值)。int x 5int a move(x)45move 是库里面的一个函数模板本质内部是进行强制类型转换当然他还涉及一些引用折叠的知识这个我们后面会细讲。6需要注意的是变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量变量表达式的属性是左值。7语法层面看左值引用和右值引用都是取别名不开空间。从汇编底层的角度看下⾯代码中 r1 和 rr1 汇编层实现底层都是用指针实现的没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的所以不要然到⼀起去理解互相佐证这样反而是陷入迷途。#includeiostream using namespace std; templateclass _Ty remove_reference_t_Ty Move(_Ty _Arg) { return static_castremove_reference_t_Ty(_Arg); } int main() { //左值可以取地址 int x 4; int* p1 x; int** pp1 p1; int x1 x; const int a 10; const int* pa1 a; pa1 p1;//权限变小。 string s1 (float); s1[0] d; //左值引用给左值取别名 int r1 x; int* r2 p1; int r3 *p1; string r4 s1; char r5 s1[0]; //右值不能取地址 int num1 3; int num2 4; //常见的右值 4, fmin(num1, num2); string(double);//匿名对象 num1 num2; //右值引用给右值取别名 int rr1 4; double rr2 fmin(num1, num2); string rr3 string(double); int rr4 num1 num2; //左值引用不能直接引用右值但是const左值引用可以引用右值 const int rc1 10; const double rc2 fmin(num1, num2); const string rc3 string(double); const double rc4 num1 num2; //右值引用不能直接引用左值但是右值引用可以引用move(左值) int rm1 move(x); int* rm2 move(p1); int rm3 move(*p1); string rm4 move(s1); char rm5 move(s1[0]); //需要注意的是变量表达式都是左值属性也就意味着一个右值被右值引用绑定后 //右值引用变量变量表达式的属性是左值。 cout rm4 endl;//00CFF77C cout s1 endl; //00CFF77C cout rm1 endl;//00B1FAEC cout x endl; //00B1FAEC //注意rm1变量的属性是左值所以不能被右值引用绑定在move一下。 int rmx1 move(rm1); cout rmx1 endl;//00B1FAEC return 0; }2.3引用延长生命周期右值引用可以为临时对象延长生命周期const的左值引用也能延长临时对象声明周期但这些对象无法被修改。#includeiostream #includestring using namespace std; int main() { string s1 abcd; string s2 1234; //string r1 s1 s2; //右值不能绑定到左值引用。 const string r1 s1 s2; //const左值引用延长生命周期 //r1 xyz; //const不能修改 string rr1 s1 s2; //右值延长生命周期 rr1 xyz; //可以修改 cout rr1 endl; return 0; }2.4左值和右值的参数匹配1C98 中我们实现一个 const 左值引用作为参数的函数那么实参传递左值和右值都可以匹配。2C11 以后分别重载左值引用、const 左值引用、右值引用作为形参的 f 函数那么实参是左值会匹配 f (左值引用)实参是 const 左值会匹配 f (const 左值引用)实参是右值会匹配 f (右值引用)。3右值引用变量在用于表达式时属性是左值这个设计这里会感觉很怪下一小节我们讲右值引用的使用场景时就能体会这样设计的价值了。#includeiostream using namespace std; void f(int a) { cout void f(int a)----左值引用 a endl; } void f(const int a) { cout void f(const int a)----const左值引用 a endl; } void f(int a) { cout void f(int a)----右值引用 a endl; } int main() { int x 5; const int y 20; f(x); //调用f(int a) f(11); //调用f(int a) f(y); //调用f(const int a) f(move(x)); //调用f(int a) return 0; }2.5右值引用和移动语义的使用场景2.5.1左值引用使用场景回顾左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题但是有些场景不能使用传左值引用返回如 addStrings 和 generate 函数C98 中的解决方案只能是被迫使用输出型参数解决。那么 C11 以后这里可以使用右值引用做返回值解决吗显然是不可能的因为这里的本质是返回对象是一个局部对象函数结束这个对象就析构销毁了右值引用返回也无法改变对象已经析构销毁的事实。#includeiostream #includestring #includealgorithm using namespace std; class Solution { public: string addStrings(string num1, string num2) { string str; int end1 num1.size() - 1; int end2 num2.size() - 1; str.reserve(max(end1 1, end2 1) 1); int carry 0; while (end1 0 || end2 0) { int x end1 0 ? num1[end1--] - 0 : 0; int y end2 0 ? num2[end2--] - 0 : 0; int ret x y carry; carry ret / 10; ret % 10; str (ret 0); } if (carry 1) { str 1; } reverse(str.begin(), str.end()); return str; } }; int main() { cout Solution().addStrings(string(1234), string(9999)) endl; return 0; }2.5.2移动构造和移动赋值1移动构造函数是一个构造函数与其他构造函数构成函数重载它的第一个默认参数是这个类类型的引用不同的是要求它的参数是右值引用如果有其他参数必须要有缺省值。2移动赋值是一个赋值运算符的重载与拷贝赋值构成函数重载它的第一个默认参数是这个类类型的引用不同的是要求它的参数是右值引用。3对于像 string/vector 这样的深拷贝的类或者包含深拷贝的成员变量的类移动构造和移动赋值才有意义因为移动构造和移动赋值的参数都是右值引用的类型他的本质是要 “窃取” 引用的右值对象的资源而不是像拷贝构造和拷贝赋值那样去拷贝资源从而提高效率。下面的 zsw::string 样例实现了移动构造和移动赋值我们需要结合场景理解。2.5.3右值引用和移动语义在传参中的提效查看 STL 文档我们发现 C11 以后容器的 push 和 insert 系列的接口都增加了右值引用版本。当实参是一个左值时容器内部继续调用拷贝构造进行拷贝将对象拷贝到容器空间中的对象。当实参是一个右值容器内部则调用移动构造窃取右值对象的资源到容器空间的对象上。把我们之前模拟实现的 bit::list 拷贝过来支持右值引用参数版本的 push_back 和 insert。其实这里还有一个 emplace 系列的接口但是这个涉及可变参数模板我们需要把可变参数模板讲解以后再讲解 emplace 系列的接口。gitee2.6类型分类1c11以后对类型进行了进一步划分右值被划分为纯右值pure value简称prvalue和将亡值expiring value简称xvalue。2纯右值是指那些字面值常量或求值结果相当于字面值或是一个不具名的临时对象。如 42、true、nullptr 或者类似 str.substr (1, 2)、str1 str2 传值返回函数调用或者整形 a、baab 等。纯右值和将亡值 C11 中提出的C11 中的纯右值概念划分等价于 C98 中的右值。3将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达如 move (x)、static_castX(x)。对一个左值强转成右值。4泛左值 (generalized value简称 glvalue)泛左值包含将亡值和左值。2.7引用折叠1C 中不能直接定义引用的引用如 int r i; 这样写会直接报错通过模板或 typedef 中的类型操作可以构成引用的引用。2通过模板或 typedef 中的类型操作可以构成引用的引用时这时 C11 给出了一个引用折叠的规则右值引用的右值引用折叠成右值引用所有其他组合均折叠成左值引用。#includeiostream using namespace std; int main() { typedef int lref; typedef int rref; //int x;//这样写会报错 int n 10; lref r1 n; //r1的类型是int ( 左值引用碰到左值引用左值引用 ) lref r2 n; //r2的类型是int ( 左值引用碰到右值引用左值引用 ) rref r3 n; //r3的类型是int ( 右值引用碰到左值引用左值引用 ) rref r4 1; //r4的类型是int( 右值引用碰到右值引用右值引用 ) rref r5 move(n);//r5的类型是int( 右值引用碰到右值引用右值引用) return 0; }3像 f2 这样的函数模板中T x 参数看起来是右值引用参数但是由于引用折叠的规则他传递左值时就是左值引用传递右值时就是右值引用有些地方也把这种函数模板的参数叫做万能引用。#includeiostream using namespace std; templateclass T void f1(T x) { } templateclass T void f2(T x) { } int main() { int n 0; // void f1(T x) //没有折叠实例化为 void f1(int x) f1int(n); //f1int(0);//报错左值引用引用右值 //折叠----实例化为 void f1(int x) f1int(n); //f1int(0);//报错左值引用引用右值 //折叠----实例化为 void f1(int x) f1int(n); //f1int(0);//报错左值引用引用右值 //没有折叠实例化为 void f1(const int x) #pragma warning(push) #pragma warning(disable:26800) f1const int(n); #pragma warning(pop) f1const int(0); //折叠----实例化为 void f1(const int x) f1const int(n); f1const int(0); //折叠----实例化为 void f1(const int x) f1const int(n); f1const int(0); // void f2(T x) //没有折叠实例化为 void f2(int x) //f2int(n);//报错右值引用引用左值 f2int(0); //折叠----实例化为 void f2(int x) f2int(n); //f2int(0);//报错左值引用引用右值 //折叠----实例化为 void f2(int x) //f2int(n);//报错右值引用引用左值 f2int(0); return 0; }4Function (T t) 函数模板程序中假设实参是 int 右值模板参数 T 的推导 int实参是 int 左值模板参数 T 的推导 int再结合引用折叠规则就实现了实参是左值实例化出左值引用版本形参的 Function实参是右值实例化出右值引用版本形参的 Function。#includeiostream using namespace std; templateclass T void func(T y) { int a 0; T x a; //x; cout a endl; cout x endl; cout a endl; cout x endl; } int main() { //10是右值推导出T为int模板实例化为void func(int y) func(10); //右值 //a是左值推导出t为int引⽤折叠模板实例化为void func(int y) int a; func(a); //左值 //std::move(b)是右值推导出t为int模板实例化为void func(int y) int b; func(std::move(b)); //右值 //c是左值推导出T为const int引⽤折叠模板实例化为void func(const int y) const int c 5; func(c);//会报错func函数内部 x不能 //const左值 //std::move(c)是右值推导出T为const int模板实例化为void func(const int y) func(std::move(c));//会报错func函数内部 x不能 //const右值 return 0; }2.8完美转发1func (T y) 函数模板程序中传左值实例化以后是左值引用的 func 函数传右值实例化以后是右值引用的 func 函数。2但是结合我们前面的讲解变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量表达式的属性是左值也就是说 func 函数中 y 的属性是左值那么我们把 y 传递给下一层函数 Fun那么匹配的都是左值引用版本的 Fun 函数。这里我们想要保持 y 对象的属性就需要使用完美转发实现。3template T forward (typename remove_referenceT::type arg);4template T forward (typename remove_referenceT::type arg)5完美转发 forward 本质是一个函数模板他主要还是通过引用折叠的方式实现下面示例中传递给 func 的实参是右值T 被推导为 int没有折叠forward 内部 y 被强转为右值引用返回传递给 func 的实参是左值T 被推导为 int引用折叠为左值引用forward 内部 y 被强转为左值引用返回。是左值引用传过去还是左值属性是右值引用传过去还是右值属性。#includeiostream using namespace std; void Fun(int x) { cout 左值引用 endl; } void Fun(const int x) { cout const 左值引用 endl; } void Fun(int x) { cout 右值引用 endl; } void Fun(const int x) { cout const 右值引用 endl; } templateclass T void func(T t) { Fun(t); cout ~完美转发~ endl; Fun(forwardT(t)); cout ************************** endl; } int main() { //10是右值推导出T为int模板实例化为void func(int y) func(10); //右值 //a是左值推导出t为int引⽤折叠模板实例化为void func(int y) int a; func(a); //左值 //std::move(b)是右值推导出t为int模板实例化为void func(int y) int b; func(std::move(b)); //右值 //c是左值推导出T为const int引⽤折叠模板实例化为void func(const int y) const int c 5; func(c);// //const左值 //std::move(c)是右值推导出T为const int模板实例化为void func(const int y) func(std::move(c));// //const右值 return 0; }
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻