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

C++第四十二弹---C++11新特性深度解析:让你的代码更现代、更高效(中)

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 右值引用和移动语义

1.1 左值引用和右值引用

1.2 左值引用与右值引用比较

1.3 右值引用使用场景和意义

1.4 右值引用引用左值及其一些更深入的使用场景分析

1.5 完美转发


1 右值引用和移动语义


1.1 左值引用和右值引用


传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名
 

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

代码演示

int main()
{// 左值是一个表达式,可以取地址// 左值和右值,能否取地址// 左值:能取地址// 右值:不能取地址int a = 10;int b = a;const int c = 10;int* p = &a;vector<int> v(10, 1);v[1];cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &(*p) << endl;cout << &(v[1]) << endl;return 0;
}

测试结果 

以上表达式均能够获取它的地址,因此均为左值。 

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址右值引用就是对右值的引用,给右值取别名

代码演示

int main()
{// 一下几个都是常见的右值// 10、string("1111")、to_string("1111")、x+ydouble x = 1.1;double y = 2.2;// 以下右值取地址会报错//cout << &10 << endl;//cout << &(string("1111")) << endl;//cout << &(to_string("1111")) << endl;//cout << &(x + y) << endl;// 以下几个都是对右值的右值引用double&& rref1 = x + y;string&& rref2 = string("11111");string&& rref3 = to_string(1111);int&& rref4 = 10;// 左值引用能否给右值取别名 ---不能,左值引用加const就能给右值取别名//string& ref1 = string("11111");// 错误const string& ref1 = string("11111");// 右值引用能否给左值取别名 ---不能,但是可以给move以后的左值取别名string s1("1111");string&& ref2 = move(s1);return 0;
}

测试结果 

右值引用,给值取别名

  • 纯右值(内置类型)
  • 将亡值(自定义类型) 

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{double x = 1.1, y = 2.2;int&& rr1 = 10;const double&& rr2 = x + y;rr1 = 20;rr2 = 5.5;  // 报错return 0;
}

给rr2赋值结果 

屏蔽rr2赋值结果

1.2 左值引用与右值引用比较


左值引用总结:

  • 1. 左值引用只能引用左值,不能引用右值。
  • 2. 但是const左值引用既可引用左值,也可引用右值。
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

右值引用总结:

  • 1. 右值引用只能右值,不能引用左值。
  • 2. 但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

1.3 右值引用使用场景和意义


前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

我们通过自己实现的string类来验证!!!

namespace lin
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str) -- 构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造// 左值string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};lin::string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}lin::string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}

左值引用的使用场景:

做参数和做返回值都可以提高效率。

void func1(lin::string s)
{}
void func2(const lin::string& s)
{}
int main()
{lin::string s1("hello world");// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值func1(s1);func2(s1);// string operator+=(char ch) 传值返回存在深拷贝// string& operator+=(char ch) 传左值引用没有拷贝提高了效率s1 += '!';return 0;
}

左值引用的短板:

但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:lin::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

代码演示

int main()
{// 只调用了一次C字符串构造,编译器直接优化结果,VS2022环境lin::string ret = lin::to_string(1234);cout << ret.c_str() << endl;// 调用两次C字符串构造 + 赋值重载lin::string ret1;ret1 = lin::to_string(1478);return 0;
}

测试结果 

右值引用和移动语义解决上述问题:

在lin::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

// 移动构造
// 右值(将亡值)
string(string&& s):_str(nullptr)
{cout << "string(string&& s) -- 移动构造" << endl;swap(s);
}

不仅仅有移动构造,还有移动赋值:

在lin::string类中增加移动赋值函数,再去调用lin::to_string(1234),不过这次是将
lin::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。

1.4 右值引用引用左值及其一些更深入的使用场景分析


按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 #include<utility> 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

代码演示

int main()
{// 构造lin::string s1("111111111111111");// 赋值重载lin::string s2 = s1;// 移动构造lin::string s3 = move(s1);return 0;
}

监视窗口 

测试结果 

注意:使用move()函数会将原来对象的数据转移走,请谨慎使用。 

1.5 完美转发

模板中的 && 万能引用

代码演示

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
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<typename T>
void PerfectForward(T&& t)
{Fun(t);// 直接使用参数传递,全部都是左值
}// 实现传什么参数,调用对应的函数
int main()
{PerfectForward(10);           int a;PerfectForward(a);            PerfectForward(std::move(a)); const int b = 8;PerfectForward(b);		      PerfectForward(std::move(b)); return 0;
}

测试结果 

引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,因此如果直接使用参数传递,得到的结果就是全部打印左值引用。 

 std::forward 完美转发在传参的过程中保留对象原生类型属性。

代码演示

将PerFectForward函数的函数体加上完美转发,则可以解决上面问题。

template<typename T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));// 使用完美转发,传什么值调用什么函数
}

测试结果

使用完美转发可以达到传什么参数,调用什么函数。 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【conda】导出和重建 Conda 环境
  • mysql 一主一从数据库的配置文件
  • GPS北斗授时服务器(网络时钟系统)助力金融领域
  • windows权限维持汇总
  • 【ubuntu24.04】AX210/MT9621/USB网络共享访问无线网络
  • 深入解析Node.js中的new URL()构造函数:功能、属性、方法与使用技巧
  • OpenStack——keystone认证服务
  • Linux 流式DMA映射(DMA Streaming Mapping)
  • 【Qt笔记】QToolButton控件详解
  • 有哪些内部知识库类似钉钉,满足企业多样化需求?
  • 服务器(百度云)部署项目(jar包)
  • 基于spring Boot的网上报修平台的设计和实现---附源码94800
  • 深度学习学习经验——全连接神经网络(FCNN)
  • 国内外大模型汇总:Open AI大模型、Google大模型、Microsoft大模型、文心一言大模型、通义千问大模型、字节豆包大模型、智普清言大模型
  • Aiseesoft Data Recovery for Mac:专业级数据恢复解决方案
  • [笔记] php常见简单功能及函数
  • 【5+】跨webview多页面 触发事件(二)
  • Cumulo 的 ClojureScript 模块已经成型
  • Javascript弹出层-初探
  • JavaScript类型识别
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Map集合、散列表、红黑树介绍
  • MySQL几个简单SQL的优化
  • python docx文档转html页面
  • tab.js分享及浏览器兼容性问题汇总
  • yii2权限控制rbac之rule详细讲解
  • 对JS继承的一点思考
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 欢迎参加第二届中国游戏开发者大会
  • 技术胖1-4季视频复习— (看视频笔记)
  • 入门到放弃node系列之Hello Word篇
  • 少走弯路,给Java 1~5 年程序员的建议
  • 什么是Javascript函数节流?
  • 详解移动APP与web APP的区别
  • 学习JavaScript数据结构与算法 — 树
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 06-01 点餐小程序前台界面搭建
  • kubernetes资源对象--ingress
  • 函数计算新功能-----支持C#函数
  • ​Java基础复习笔记 第16章:网络编程
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (2015)JS ES6 必知的十个 特性
  • (21)起落架/可伸缩相机支架
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (算法)Travel Information Center
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)visual stdio 书签功能介绍
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET IoC 容器(三)Autofac