当前位置: 首页 > news >正文

【C++】C++11(一)

1. C++11的发展历史

2. 列表初始化

2.1 C++98传统的{}

	// C++98支持的 int a1[] = { 1, 2, 3, 4, 5 };int a2[5] = { 0 };Point p = { 1, 2 };

2.2 C++11中的{}

  • {}初始化也叫做列表初始化
  • 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化 了以后变成直接构造
  • {}初始化的过程中,可以省略掉=
int main()
{// C++11⽀持的 // 内置类型⽀持 int x1 = { 2 };// ⾃定义类型⽀持 // 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象 // 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造Date d1 = { 2025, 1, 1 };// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象 const Date& d2 = { 2024, 7, 25 };// 可以省略掉= Point p1{ 1, 2 };int x2{ 2 };Date d6{ 2024, 7, 25 };const Date& d7{ 2024, 7, 25 };// 不⽀持,只有{}初始化,才能省略= // Date d8 2025;vector<Date> v;v.push_back(d1);v.push_back(Date(2025, 1, 1));// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐ v.push_back({ 2025, 1, 1 });return 0;
}

2.3 C++11中的std::initializer_list

  • C++11库中提出了一个std::initializer_list的类,本质是底层开一个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束
  • std::initializer_list支持迭代器遍历,文档:initializer_list
  • 容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的 {x1,x2,x3...} 进行初始化
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{std::initializer_list<int> mylist;mylist = { 10, 20, 30 };cout << sizeof(mylist) << endl;// 这⾥begin和end返回的值initializer_list对象中存的两个指针 // 这两个指针的值跟i的地址跟接近,说明数组存在栈上 int i = 0;cout << mylist.begin() << endl;cout << mylist.end() << endl;cout << &i << endl;// {}列表中可以有任意多个值 // 这两个写法语义上还是有差别的,第⼀个v1是直接构造, // 第⼆个v2是构造临时对象+临时对象拷⻉v2+优化为直接构造 vector<int> v1({ 1,2,3,4,5 });vector<int> v2 = { 1,2,3,4,5 };const vector<int>& v3 = { 1,2,3,4,5 };// 这⾥是pair对象的{}初始化和map的initializer_list构造结合到⼀起⽤了 map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };// initializer_list版本的赋值⽀持 v1 = { 10,20,30,40,50 };return 0;
}

3. 右值引用和移动语义

3.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';
cout << &c << endl;
cout << (void*)&s[0] << endl;// 右值:不能取地址 
double x = 1.1, y = 2.2;
// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值 
10;
x + y;
fmin(x, y);
string("11111");
//cout << &10 << endl;
//cout << &(x+y) << endl;
//cout << &(fmin(x, y)) << endl;
//cout << &string("11111") << endl;

3.2 左值引用和右值引用

  • 左值引用就是给左值取别名,右值引用就是给右值取别名
  • 左值引用不能直接引用右值,但是const左值引用可以引用右值
  • 右值引用不能直接引用左值,但是右值引用可以引用move(左值),move是库里面的一个函数模板,本质内部是进行强制类型转换
  • 变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值
template <class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg)
{ // forward _Arg as movablereturn static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
	// 左值:可以取地址 // 以下的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;// 左值引⽤给左值取别名 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");// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值 const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值) int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);string&& rrx5 = (string&&)s;// b、r1、rr1都是变量表达式,都是左值 cout << &b << endl;cout << &r1 << endl;cout << &rr1 << endl;// 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除⾮move⼀下 int& r6 = r1;// int&& rrx6 = rr1;int&& rrx6 = move(rr1);return 0;

3.3 引用延长生命周期

右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生存期,但这些对象无法被修改

3.4 左值和右值的参数匹配

C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参会分别匹配 

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";
}int main()
{int i = 1;const int ci = 2;f(i); // 调⽤ f(int&) f(ci); // 调⽤ f(const int&) f(3); // 调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&) f(std::move(i)); // 调⽤ f(int&&) // 右值引⽤变量在⽤于表达式时是左值 int&& x = 1;f(x); // 调⽤ f(int& x) f(std::move(x)); // 调⽤ f(int&& x) return 0;
}

3.5 右值引用和移动语义的使用场景

1) 左值引用主要使用场景

  • 左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值
  • 左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如addStrings和generate函数

2) 移动构造和移动赋值

  • 移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第⼀个参数是该类类型的引 用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值
  • 移动赋值是一个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函 数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用
  • 深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率
// 移动构造 
string(string&& s)
{cout << "string(string&& s) -- 移动构造" << endl;swap(s);
}// 移动赋值 
string& operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;
}
int main()
{bit::string s1("xxxxx");// 拷贝构造 bit::string s2 = s1;// 构造+移动构造,优化后直接构造 bit::string s3 = bit::string("yyyyy");// 移动构造 bit::string s4 = move(s1);cout << "******************************" << endl;return 0;
}

3) 右值引用和移动语义解决传值返回问题

存在编译器优化现象,编译器会优化拷贝构造过程,变成直接构造

右值对象构造,只有拷贝构造 没有移动构造的场景
  • 图1展示了vs2019debug环境下编译器对拷贝的优化,左边为不优化的情况下,两次拷贝构造,右 边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造
  • 在vs2019的release和vs2022的debug和release,会直接优化将str对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为一,变为直接构造
  • inux下可以将下面代码拷贝到test.cpp,编译时用g++ test.cpp -fno-elideconstructors 的方式关闭构造优化,运行结果可以看到图1左边没有优化的两次拷贝

图1
右值对象构造,有拷贝构造,也有移动构造的场景

移动构造本质是将ret对象移动str的资源,再将str置空

右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景
  • 图4左边是vs2019debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,一次拷贝构造,一次拷贝赋值
  • 在vs2019的release和vs2022的debug和release,代码会进⼀步优化,直接构造要返回的临时对象,str本质是临时对象的引用,底层角度用指针实现。运⾏结果的角度,可以看到str的析构是在赋值以后,说明str就是临时对象的别名

图四
右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景

4) 右值引用和移动语义如何在传参中的提效?

当实参是一个左值时(左值一般是有持久状态),容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象 

当实参是一个右值(因为右值一般是创建的临时对象),容器内部则调用移动构造,右值对象的资源到容器空间的对象上

3.6 类型分类

  • C++11以后,进⼀步对类型进行了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值 (expiring value,简称xvalue)
  • 纯右值是指那些字面值常量或求值结果相当于字面值或是一个不具名的临时对象。如: 42、 true、nullptr 或者类似 str.substr(1, 2)、str1 + str2 传值返回函数调用,或者整 形 a、b,a++,a+b 等
  • 将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达,如 move(x)、static_cast(x)
  • 泛左值(generalized value,简称glvalue),泛左值包含将亡值左值

3.7 引用折叠

  • C++中不能直接定义引用的引用如 int& && r = i; ,这样写会直接报错,通过模板或typedef中的类型操作可以构成引用的引用
  • 通过模板或typedef中的类型操作可以构成引用的引用时,这时C++11给出了⼀个引用折叠的规 则:右值引用的右值引用折叠成右值引用所有其他组合均折叠成左值引用
// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤ 
template<class T>
void f1(T& x)
{}// 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤ 
template<class T>
void f2(T&& x)
{}
int main()
{typedef int& lref;typedef int&& rref;int n = 0;lref& r1 = n; // r1 的类型是 int& lref&& r2 = n; // r2 的类型是 int& rref& r3 = n; // r3 的类型是 int& rref&& r4 = 1; // r4 的类型是 int&& // 没有折叠->实例化为void f1(int& x) f1<int>(n);f1<int>(0); // 报错 // 折叠->实例化为void f1(int& x) f1<int&>(n);f1<int&>(0); // 报错 // 折叠->实例化为void f1(int& x) f1<int&&>(n);f1<int&&>(0); // 报错// 折叠->实例化为void f1(const int& x) f1<const int&>(n);f1<const int&>(0);// 折叠->实例化为void f1(const int& x) f1<const int&&>(n);f1<const int&&>(0);// 没有折叠->实例化为void f2(int&& x) f2<int>(n); // 报错 f2<int>(0);// 折叠->实例化为void f2(int& x) f2<int&>(n);f2<int&>(0); // 报错 // 折叠->实例化为void f2(int&& x) f2<int&&>(n); // 报错 f2<int&&>(0);return 0;
}

像f2这样的函数模板中,T&&x参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左 值时就是左值引用,传递右值时就是右值引用,也把这种函数模板的参数叫做万能引用

template<class T>
void Function(T&& t)
{int a = 0;T x = a;//x++;cout << &a << endl;cout << &x << endl << endl;
}
int main()
{// 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;// b是左值,推导出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 右值 return 0;
}

Function(T&&t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参的 Function,实参是右值,实例化出右值引用版本形参的Function

3.8 完美转发

  • Function(T&&t)函数模板程序中,传左值实例化以后是左值引用的Function函数,传右值实例化以后是右值引用的Function函数
  • 但由于变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引用版本的Fun函数。想要保持t对象的属性, 就需要使用完美转发实现
  • template T&& forward (typename remove_reference::type& arg);
  • template T&& forward (typename remove_reference::type&& arg);
  • 完美转发forward本质是一个函数模板,他主要还是通过引用折叠的方式实现,下面示例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引用返回;传递给 Function的实参是左值,T被推导为int&,引用折叠为左值引用,forward内部t被强转为左值引用返回
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{ // forward an lvalue as either an lvalue or an rvaluereturn static_cast<_Ty&&>(_Arg);
}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; }
template<class T>
void Function(T&& t)
{//Fun(t);Fun(std::forward<T>(t));}
int main()
{// 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(b); // const 左值 // std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)Function(std::move(b)); // const 右值 return 0;
}


文章转载自:

http://ZkSKuaFX.xpLng.cn
http://PxDnPhAE.xpLng.cn
http://evP9zWSd.xpLng.cn
http://zeknqTE1.xpLng.cn
http://JNAUooI3.xpLng.cn
http://IAFkFehK.xpLng.cn
http://BLAll6JL.xpLng.cn
http://94NlFsCp.xpLng.cn
http://sx4CyoOL.xpLng.cn
http://unlrtPuT.xpLng.cn
http://ZkY3Ol6U.xpLng.cn
http://UGel5kyd.xpLng.cn
http://7a9vClSJ.xpLng.cn
http://CoaLZU2y.xpLng.cn
http://IzNCniNt.xpLng.cn
http://vVaECrgn.xpLng.cn
http://pxjnWx2d.xpLng.cn
http://4Y2XCgLa.xpLng.cn
http://4p8whVF7.xpLng.cn
http://5SMWvDLJ.xpLng.cn
http://tNJuPdIi.xpLng.cn
http://WJgQ2mX0.xpLng.cn
http://CRlGTcEc.xpLng.cn
http://CfaK4ndH.xpLng.cn
http://S1DmP9g4.xpLng.cn
http://kZWe3dIZ.xpLng.cn
http://FEiRBneC.xpLng.cn
http://l1z3XDGy.xpLng.cn
http://3rJd9ZX6.xpLng.cn
http://59YUzQCC.xpLng.cn
http://www.dtcms.com/a/386929.html

相关文章:

  • 两数的乘积 = 最大公约数 × 最小公倍数
  • 【Block总结】FDConv,多频动态调制卷积模块|即插即用|CVPR2025
  • Python 爬虫入门:如何抓取电商网站商品数据
  • 2025年上半年软考系统架构设计师备考指南
  • 双反向传播训练光子神经网络(未做完)
  • Java和rust的AES加解密算法互相转化,秘钥key格式不一致带来的问题
  • Altium Designer(AD24)导入DDB库文件(Protel 99SE)方法
  • GEO数据集编号,我为您整理了对应的芯片平台信息的获得办法
  • 《漫威争锋》新内容曝光:刀锋战士预热登场及多项更新
  • 【Redis】-- 哨兵
  • C++八大排序
  • 特殊文件,日志
  • Linux命令大全(文件管理)
  • jira工具
  • 易语言制表符替换为空格如何替换?
  • 2020考研数学(二)真题
  • JVM-对象内存布局
  • leetcode 5 最长回文子串
  • [笔记] 系统分析师 第十二章 软件架构设计(分析师主要工作)
  • 健康大数据管理与服务专业发展潜力大吗?
  • 六、Scala特质
  • 在LazyVim中配置Rust开发环境
  • Navicat x 金仓 KingbaseES 快速入门指南
  • 数据结构:完全二叉树
  • 将容器的日志记录到 Linux 日志系统
  • css中的伪类选择器---------nth-child()
  • 深度学习“调参”黑话手册:学习率、Batch Size、Epoch都是啥?
  • Vue: 组件 Props
  • spring通过Spring Integration实现tcp通信
  • 改革企业治理架构,构建国有企业全面预算管理体系