C++11右值引用

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

左值与左值引用

        左值是一个表示数据的表达式(如变量名解引用的指针),

        1.左值可以获取它的地址。

        2.左值可以对它赋值,但定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

        3.左值可以出现赋值符号的左边,也可以右边

        左值引用就是给左值的引用,给左值取别名。

int a = 0;
int& b = a;

右值与右值引用

        右值也是一个表示数据的表达式(如:字面常量表达式返回值函数返回值(这个不能是左值引用返回)等等)

        1.右值不能取地址。(与左值最重要的区别)

        2.右值可以对它赋值,不可以改变它

        3.右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边

        右值引用就是对右值的引用,给右值取别名。

double func()
{return 1.3;
}
int main()
{double x = 1.1;double y = 1.2;//常见的右值10;//字面常量x + y;//表达式返回值func();//函数返回值//右值引用int&& a1 = 10;double&& a2 = x + y;double&& a3 = func();return 0;
}

底层上 

总结:

语法上,引用都是取别名,不开空间,左值引用是给左值取别名,右值引用是给右值取别名。

底层上,本质上都是指针,

        1.左值引用是存当前左值的地址。

        2.右值引用是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址。

右值引用与左值引用特殊使用情况

        左值引用不能直接给右值取别名,但是const 左值引用可以给右值取别名。

        右值引用不能直接给左值取别名,但是move(左值)可以用右值引用进行取别名。

double func()
{return 1.3;
}
int main()
{//左值引用给右值取别名//int& r1 = func();错误//int& r2 = 10;错误const int& r1 = func();const int& r2 = 10;//右值引用给左值取别名int x = 0;//int&& rr1 = x;错误int&& rr1 = move(x);//需要move一下return 0;
}

右值引用所解决的问题(移动语义)

左值引用解决了什么问题?

        1.传参拷贝的问题全部解决

                传参时,代替c语言中指针的作用。

        2.传返回值的问题解决了一部分

                解决了传返回值时候产生拷贝的问题,减少了一次拷贝(当返回值是一个大的类对象时,减少拷贝能减少很大开销)。

        但是局部对象返回(出了作用域销毁)的拷贝问题未解决。

        在C++98中对传左值和右值都会被视为const类型的对象

例如:

void func(const int& i)
{cout << "void func(const int& i)" << endl;
}
//void func(int&& i)
//{
//	cout << "void func(int&& i)" << endl;
//}
int main()
{int a = 0;func(a);//传左值func(10);//传右值return 0;
}

        而在C++11中,有了右值引用的概念后,我们可以对函数写出右值引用的版本 ,这时传右值就会调右值引用版本的函数,传左值就会调左值引用版本的函数。

例如:

void func(const int& i)
{cout << "void func(const int& i)" << endl;
}
void func(int&& i)
{cout << "void func(int&& i)" << endl;
}
int main()
{int a = 0;func(a);//传左值func(10);//传右值return 0;
}

        当然也可以使用move将左值属性转变成右值属性,来当成右值传参。

void func(const int& i)
{cout << "void func(const int& i)" << endl;
}
void func(int&& i)
{cout << "void func(int&& i)" << endl;
}
int main()
{int a = 0;func(a);//传左值func(10);//传右值func(move(a));//使用move将左值转成右值return 0;
}

        本质上是对传入参数的类型(左值or右值)进行识别。

C++11对右值概念的解释可以形象地理解为以下两种:

1.纯右值(内置类型的右值)如: 1,a+b

2.将亡值(自定义类型的右值)如:匿名对象,传值返回函数/to_string(上面的例子)

场景一

string拷贝构造的场景——拷贝构造移动构造版本

拷贝构造
        //拷贝构造——左值string(const string& tmp){cout << "string(const string& tmp)——深拷贝" << endl;_str = new char[tmp._capacity + 1];strcpy(_str, tmp._str);_size = tmp._size;_capacity = tmp._capacity;}

        如果传入的对象tmp是将亡值,深拷贝是一种浪费资源的行为,因为深拷贝后tmp对象就销毁了。

移动构造

        使用右值引用进行移动构造。

		//移动构造——右值(将亡值)string(string&& tmp){cout << "string(string&& tmp)——移动拷贝" << endl;swap(tmp);}

        本质上是识别传入的值是右值(将亡值),然后函数内部对该右值(将亡值)进行特殊处理。

 注:右值被右值引用后,右值引用的属性是左值

总结:

浅拷贝的类不需要移动构造。

原因:通常是一些内置类型,直接拷贝代价不大。

深拷贝的类需要移动构造。

原因:传入的是将亡值时,重新开空间构造对于一些大对象代价过大,不如直接将 将亡值 的资源转移给要构造的对象。

 场景二

        在这个场景中,C++11有了右值引用和移动语义,大大减少了传值返回的拷贝。

        考虑这样一个场景,在string容器中有个成员函数是 to_string ,它的功能是将一个数值转换成string类型的字符串,以下是其简要模拟实现。

        string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}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;}

        C++98中,需要两次拷贝,但被编译器优化成一次拷贝。

        C++11后加入右值引用,需要一次拷贝,但被编译器优化成无拷贝。

场景三 

拷贝赋值重载的移动赋值版本(强行去掉编译器优化)

赋值拷贝
		//赋值拷贝(现代写法)string& operator=(string& tmp){cout << "string& operator=(string& tmp)——深拷贝" << endl;string s(tmp);swap(s);return *this;}
int main()
{string s;s = to_string(10);return 0;
}

        相比于上一个场景,先定义后赋值,可以避开编译器优化,这样我们直接可以看到拷贝过程。

        结果是两次拷贝,刚好是如下图这样的情况。 

移动赋值
		//移动拷贝string& operator=(string&& tmp){cout << "string& operator=(string&& tmp)——移动赋值" << endl;swap(tmp);return *this;}
int main()
{string s;s = to_string(10);return 0;
}

        这样就是一次深拷贝,一次移动赋值不产生拷贝。

        移动将亡值的资源,并且把不要的空间给将亡值,让将亡值释放时刚好释放不要的空间,一举两得。

总结:左值引用没有解决的问题,右值引用解决了,深拷贝对象传值返回只需要移动资源,代价很低

        C++11以后,所有的容器都增加了移动构造移动赋值和插入。有值转移的地方都可以考虑右值引用版本。

场景四

        特殊细节:右值被右值引用之后,右值引用r的属性是左值。
int main()
{//右值被右值引用以后,右值引用r是左值。int&& r = 10;r++;//左值可以被修改
}

        以list容器中的复用insert的push_back为例。

	    iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return newnode;}
		void push_back(const T& x){insert(end(), x);}

        以下是右值引用版本

	    iterator insert(iterator pos, const T&& x){//右值引用版本代码……return newnode;}
		//右值引用版本void push_back(T&& x){insert(end(), x);}
然而在push_back中传入右值时,再去调insert函数,结果调用的是insert左值引用版本。
原因是右值传入时, 右值被右值引用,此时右值引用x是一个左值所以传入insert的是一个左值。

如何解决?
        法一:强行把左值move成右值
		//右值引用版本void push_back(T&& x){insert(end(), move(x));//将左值move成右值}

         法二:完美转发

完美转发

模版中的&&——万能引用

        模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。

例子:

template<typename T>
void PerfectForward(T&& t)//这里是万能引用,传右值就是右值引用,传左值就是左值引用
{//代码
}

测试一下 

void Func(int& x)
{cout << "void Func(int& x)----左值" << endl;
}
void Func(int&& x)
{cout << "void Func(int&& x)----右值" << endl;
}
void Func(const int& x)
{cout << "void Func(const int& x)----const左值" << endl;
}
void Func(const int&& x)
{cout << "void Func(const int&& x)----const右值" << endl;
}template<typename T>
void PerfectForward(T&& t)
{Func(t);
}

int main()
{int a = 10;PerfectForward(a);//传左值PerfectForward(10);//传右值return 0;
}

但是结果

        原因是传右值时,右值被右值引用,右值引用属性是左值,导致再去调Func函数时传的是左值。

        如何解决这个问题?

        前文中的一种方式是move一下,将左值move成右值,但是对于这样的场景是不适用的,这里是要测试传的是左值还是右值,而move统统把所有值变成右值,这样就失去了测试的意义(传左值结果被move成右值,导致测试结果是右值)。

        这里C++11提供了一个 std::forward 完美转发在传参的过程中保留对象原生类型属性,它的作用是保持属性:如果本身是左值就不变,如果本身是右值右值被右值引用后,右值引用是左值,转成右值,相当于move一下。

template<typename T>
void PerfectForward(T&& t)
{Func(std::forward<T>(t));
}

        这样也就弥补之前场景四的特殊细节的情况 

总结:move与forward的区别

一、move 左值属性——>右值属性

二、forward 保持属性

        1.如果本身是左值就不变

        2.如果本身是右值,右值被右值引用后,右值引用是左值,转成右值,相当于move一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/797928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++面试经典问题

常见问题&#xff1a;智能指针、多态、虚函数、STL原理、链表、排序、二叉树、设计模式、线程进程、内存 对象所有权 在接触智能指针之前首先要理解对象的所有权是什么&#xff0c;在这之前我们总是用new和delete来进行内存的申请与释放&#xff0c;在这种堆内存分配的方式中…

springboot国际化多语言

1,新建国际化多语言文件 在resources目录下新建 messages.properties 其他语言的文件 编辑messages.properties文件,下方从text切换到Resource Bundle ,即可对照着编辑多语言文件 (如果没有找到Resource Bundle,先在settings->plugins中安装Resource Bundle Editor) 2,配…

Lumos学习王佩丰Excel第二讲:单元格格式设置

今天学会GIF录制了&#xff0c;分享知识会更简便一些&#xff0c;话不多说&#xff0c;开始吧~ 一、美化表格 1、设置单元格格式的路径 从菜单栏进入&#xff1a; 选中区域&#xff08;单元格&#xff09;- 右键“设置单元格格式”&#xff1a; 2、合并单元格 合并一行 批量…

5.3 用栈翻转数组,动态规划求斐波那契数列

5.3 用栈翻转数组&#xff0c;动态规划求斐波那契数列 1. 用栈翻转数组 assume cs:code,ds:data,ss:stack data segmentarr dw 1111h,2222h,3333h,4444h,5555h,6666h,7777h,8888hres db 800 dup(0) data endsstack segmentdb 100 dup(0) stack endscode segmentstart:mov ax,…

YOLOv8模型剪枝实战:Network Slimming网络瘦身方法

课程链接&#xff1a;YOLOv8模型剪枝实战&#xff1a;Network Slimming网络瘦身方法_在线视频教程-CSDN程序员研修院 YOLOv8是一个当前非常流行的目标检测器&#xff0c;本课程使用Network Slimming&#xff08;网络瘦身&#xff09;剪枝方法对YOLOv8进行模型剪枝&#xff0c;…

力扣347. 前 K 个高频元素

思路&#xff1a;记录元素出现的次数用map&#xff1b; 要维护前k个元素&#xff0c;不至于把所有元素都排序再取前k个&#xff0c;而是新建一个堆&#xff0c;用小根堆存放前k个最大的数。 为什么是小根堆&#xff1f;因为堆每次出数据时只出堆顶&#xff0c;每次把当前最小的…

手动实现Tomcat底层机制+自己设计Servlet

文章目录 1.Tomcat整体架构分析自己理解 2.第一阶段1.实现功能2.代码1.TomcatV1.java 3.调试阶段1.阻塞在readLine导致无法返回结果 4.结果演示 3.第二阶段1.实现功能2.代码1.RequestHander.java2.TomcatV2.java 3.调试阶段1.发现每次按回车会接受到两次请求 4.结果演示 4.第三…

[dvwa] Command Injection

命令注入 0x01 low 没有过滤&#xff0c;直接利用 127.0.0.1 && ip a 函数 php_uname(mode) 动态地检查服务器的操作系统 ‘s’&#xff1a;操作系统名称 ‘n’&#xff1a;网络主机名 ‘r’&#xff1a;操作系统发行版本号 ‘v’&#xff1a;操作系统版本 ‘m’&…

书籍《笔记的方法》读后感

读完《笔记的方法》有几周的时间&#xff0c;书里有些记录的内容&#xff0c;觉得非常有价值的&#xff0c;自己的观点&#xff0c;当下读书&#xff0c;其实并没有那么高大尚&#xff0c;就是存粹陶冶下情操&#xff0c;读书还是有一定作用的&#xff0c;毕竟看书只能慢慢来&a…

淘宝API接口详解:如何高效利用API进行电商开发

淘宝API接口详解&#xff1a;如何高效利用API进行电商开发 请求示例&#xff0c;API接口接入Anzexi58 在电商行业蓬勃发展的今天&#xff0c;淘宝作为国内最大的电商平台之一&#xff0c;为商家和开发者提供了丰富的API接口。这些接口使得电商开发变得更加高效和便捷。本文将详…

【算法篇】三道题理解算法思想——认识BFS

BFS&#xff08;宽搜&#xff09; 宽度优先遍历和深度优先遍历组成了大家熟悉的搜索算法&#xff0c;这两种算法也是蓝桥杯之类竞赛题的常考思想&#xff0c;正巧马上蓝桥杯临近&#xff0c;博主也是刷了很多BFS相关的题型&#xff0c;在这篇文章中会从力扣上选取三道简单的宽搜…

一键无痕清理:高效删除Mac文件夹,释放宝贵存储空间

在当今重视隐私的时代&#xff0c;当转让或出借Mac电脑时&#xff0c;确保个人文件和敏感信息彻底清除至关重要。常规删除Mac上的文件和文件夹仅使数据看似消失&#xff0c;实际上它们仍驻留在硬盘上&#xff0c;存在被数据恢复软件找回的风险。为实现不可逆的删除效果&#xf…

【Linux】指令

1. 简单指令 whoami 显示当前登入账号名 ls /home 现在有的用户名 adduser 用户名 新加用户&#xff08;必须在root目录下&#xff09; passwd 用户名 给这个用户设置密码 userdel -r 用户名 删除这个用户 pwd 显示当前所处路径 stat 文件名 / 文件夹名 显示文件状…

五分钟快速搭建五金行业小程序商城教程解析

作为五金行业的从业者&#xff0c;你可能想要拓展线上业务&#xff0c;提供更方便快捷的购物体验给顾客。而小程序商城成为了一种非常受欢迎的方式。但是&#xff0c;你可能觉得不懂代码无法实现这样的小程序商城。现在&#xff0c;我将通过以下步骤&#xff0c;教你如何在五分…

TikTok防关联封号及操作需注意什么?

现在Tiktok对账户的管控一直非常严格。在运营Tiktok账户时&#xff0c;应该注意哪些方面来减少账户损失&#xff1f;以下是几个账户可能出现问题的原因。 网络环境问题 1.Tiktok账户对账户有严格的控制。我们经营Tiktok账户&#xff0c;手机应该模拟海外环境。一旦平台发现我…

全网短剧搜索前端源码开源分享可改自己的接口

全网短剧搜索前端源码 内含7000短剧资源(不支持在线播放&#xff09; 源码全开源&#xff0c;可以修改成自己的接口 178、226、347行修改 源码免费下载地址抄笔记 (chaobiji.cn)https://chaobiji.cn/

linux 文件提权|属性修改

文章目录 suid&#xff08;set uid&#xff09;添加文件属性查看文件属性i &#xff08;immutable&#xff09; umask suid&#xff08;set uid&#xff09; 让文件在执行的时候具有属主&#xff08;对应文件 user &#xff09;的权限 chmod 7744 temp.txt 第一位的7表示权限位…

数据库的基本使用

一、数据库的简介 RDBMS简介&#xff1a; Relational Database Management System,通过表来表示关系类型。当前主要使用两种类型的数据库:关系型数据库和非关系型数据库。所谓的关系型数据库RDBMS是建立在关系模型基础上的数据库&#xff0c;借助于集合代数等数学概念和方法来…

蓝桥杯第十三届省赛C++B组(未完)

目录 刷题统计 修剪灌木 X进制减法 【前缀和双指针】统计子矩阵 【DP】积木画 【图DFS】扫雷 李白打酒加强版 DFS (通过64%&#xff0c;ACwing 3/11&#xff09;; DFS(AC) DP&#xff08;AC&#xff09; 砍竹子(X) 刷题统计 题目描述 小明决定从下周一开始努力刷题准…

《QT实用小工具·十八》高亮发光按钮控件

1、概述 源码放在文章末尾 该项目实现了高亮发光按钮控件 可设置文本&#xff0c;居中显示。可设置文本颜色。可设置外边框渐变颜色。可设置里边框渐变颜色。可设置背景色。可直接调用内置的设置 绿色、红色、黄色、黑色、蓝色 等公有槽函数。可设置是否在容器中可移动&#…