右值和右值引用(C++11新特性)

文章目录

  • 右值VS左值
  • 右值引用VS左值引用
    • 定义
    • move函数
    • 左值引用&&右值引用 与 函数重载
    • 模板
    • 完美转发
    • 左值引用的意义
  • 移动构造&&移动赋值
    • 默认移动构造&&赋值

右值VS左值

关于什么是右值什么是左值,我们是这样判断的:

  • 右值:不能出现在等号左边的值 且 不能取地址(包括:字面常量、内置类型表达式,内置类型返回值)
  • 左值:可以取地址(包括:变量)

下面几种表述是错误的:

  • 赋值符号左边的就是左值——与下面的同一个例子😅

  • 能修改的就是左值——自定义类型且重载了operator+

    	std::string s1("123");
        std::string s2("456");
        std::string s3("789");
        s1 + s2 = s3;
    

    这是因为重载了+号,传值返回会生成一个匿名对象,然后s3是对这个匿名对象赋值了,但是这个匿名对象在这行结束之后就会销毁

  • 在赋值符号右边的就是右值——左值既能出现在左边又能出现在右边


	int add(int x, int y)
	{
		return x + y;
	}
	int a1 = 1;                       //1
	int* a2 = new int[10];            //2
	const int a3 = 1;                 //3
	int a4 = a1 + a3;                 //4
	int a5 = add(a1, a3);             //5

1、2、4、5等号右边的都是右值
1、2、3、4、5等号左边都是左值

右值又可以细分为:

  • 纯右值:内置类型表达式
  • 将亡值:自定义类型的表达值,即将被析构

这两个是后面识别右值的判别准则

右值引用VS左值引用

定义

  • 右值引用:对右值的引用
  • 左值引用:对左值的引用

注意区分:右值 VS 右值引用左值 VS 左值引用

	//左值 和 右值
	int a1 = 1;
	int* a2 = new int[10];
	const int a3 = 1;
	int a4 = a1 + a3;
	int a5 = add(a1, a3);


	//右值引用
	int&& r1 = 10;
	int&& r2 = a1 + a3;
	int&& r3 = add(a1, a3); 


	//左值引用
	int& l1 = a1;
	const int& l2 = a3;
	int*& l3 = a2;

注意

  • 右值是不可以取地址,但是右值引用是可以取地址,因为给右值取别名之后必然会存储到特定位置,也就是说右值引用是左值!📌
  • 左值引用既可以引用左值也可以引用右值——引用右值可以使用const int &a=1;
  • 右值引用只能引用右值,不能引用左值
  • 右值引用可以引用move之后的左值

move函数

move函数的唯一作用就是将左值强制转换成右值,但是使用move时将自定义类型转成右值的时候一定要注意:如果该类型支持移动赋值 或 移动构造,可能会产生一些意想不到的后果

	string s1("i am student");
    string s2(move(s1));

    cout << s1 << s1.size() << endl;
    cout << s2 << s2.size()<<endl;

在这里插入图片描述
这里s1的空间就被释放掉了,所以move自定义类型的时候一定要谨慎!

左值引用&&右值引用 与 函数重载

左值引用和右值引用可以作为函数重载的依据,

  • 如果参数是左值—— 匹配左值引用
  • 如果参数是右值——优先匹配右值引用
  • 例如const int&这种既能匹配左值又能匹配右值的,右值会优先匹配右值引用,如果没有右值引用才会匹配它。
void fun(int& a)
{
    cout << "int& a" << endl;
}

void fun(const int& a)
{
    cout << "const int& a" << endl;
}


void fun(int&& a)
{
    cout << "const int&& a" << endl;
}


int main()
{
    int a = 1;
    const int b = 2;
    fun(a);
    fun(b);
    fun(1);
}

在这里插入图片描述

模板

template<class T>
void forward(T&& t)
{
	//.........
}

在函数模板的情况下,传入不同类型的参数会如何?
模板里面的函数参数不论是&还是&& 既可以实例化成左值引用,又可以实例化成右值引用。
例如如下几个例子:

int a = 1;
    const int  b = 1;

    forward(a);               //这里T 为int   实例化后的函数参数为:int & t
    forward(move(a));          //这里T 为int   实例化后的函数参数为:int&& t


    forward(b);                 //这里T 为const int   实例化后的函数参数为:int & t
    forward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t

完美转发

我们用模板实例化的函数不管传入的是左值还是右值,在引用之后通通变成了左值。如何让函数模板在向其他函数传递参数时该如何保留该参数的左右值属性?
看一下下面这种情况:

void fun(int& a)
{
    cout << "int& a" << endl;
}
void fun(const int& a)
{
    cout << "const int& a" << endl;
}
void fun(int&& a)
{
    cout << "int&& a" << endl;
}
void fun(const int&& a)
{
    cout << "const int&& a" << endl;
}
template<class T>
void forward(T&& t)
{
    fun(t);
}
int main()
{
    int a = 1;
    const int  b = 1;

    forward(a);               //这里T 为int   实例化后的函数参数为:int & t
    forward(move(a));          //这里T 为int   实例化后的函数参数为:int&& t


    forward(b);                 //这里T 为const int   实例化后的函数参数为:int & t
    forward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t
}

结果我们发现结果为:
在这里插入图片描述
这是由于在引用之后丢失了右值的特性,我们可以使用完美转发:

void fun(int& a)
{
    cout << "int& a" << endl;
}

void fun(const int& a)
{
    cout << "const int& a" << endl;
}

void fun(int&& a)
{
    cout << "int&& a" << endl;
}



void fun(const int&& a)
{
    cout << "const int&& a" << endl;
}

template<class T>
void forward(T&& t)
{
    fun(forward<T>(t));
}

int main()
{
    int a = 1;
    const int  b = 1;

    forward(a);               //这里T 为int   实例化后的函数参数为:int & t
    forward(move(a));          //这里T 为int   实例化后的函数参数为:int&& t


    forward(b);                 //这里T 为const int   实例化后的函数参数为:int & t
    forward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t
}

这时传入参数的特性就可以获得很好的保留:
在这里插入图片描述

左值引用的意义

string function(string& s)
{
	string tmp;
	//.........
	return tmp;
}

左值引用广泛引用于函数传参,可以不用拷贝,提升了效率,但是你在函数内对s修改同时会影响到外部s。
但是返回的时候一般的情况下是需要拷贝的,如果函数返回值是引用,那么就说明返回的这个对象的生命周期要大于函数的栈帧,也就是说返回的对象出了函数作用域不能被销毁,像上文中的tmp是无法使用引用传值返回的,tmp在函数还未返回时就会调用析构函数销毁

现在有如下一个情况:如果返回的tmp经过函数内部的操作变成一个无比巨大的字符串,如果返回必然会调用拷贝构造函数,造成很大的开销。有没有什么好方法减少开销?

  • 在没学右值引用时,我们使用输出型参数,在函数作用域外将返回的对象开好,再在函数参数列表引用传入,这时就可以跳过传值返回不必要的那次拷贝构造了。

    void function(string& s,string& tmp)
    {
     //.........
    }
    

右值引用又为我们提供了一个新的方法

移动构造&&移动赋值

我们来重新看一下这个函数的返回过程:
在这里插入图片描述

string s1("123");
string ret=function(s1);   //4
 
string ret;
ret=function(s1);     //5
  1. 调用拷贝构造函数将tmp深拷贝给另一个临时对象,

    • 如果输出形式为4,那么编译器会将赋值重载优化掉,也就是将tmp直接拷贝构造给外部的ret
    • 如果输出形式为5,还会调用一次赋值重载(实际上也是深拷贝)实现ret赋值
      在这里插入图片描述
  2. 走到2的位置处,函数结束,销毁函数栈帧调用string的析构函数销毁tmp

实际上tmp里面存的就是ret需要的内容,但是ret出函数栈帧需要被销毁,所以需要将其深拷贝出来,再赋值给ret。

能不能让tmp中直接赋值给ret呢?——移动构造&&移动赋值

		string(string&& s)  //右值引用   移动构造
		{
			
			std::swap(_str, s._str);
			std::swap(_size,s._size);
			std::swap(_capacity,s._capacity);
		}
		string& operator=(string && s)   //右值引用 移动赋值
		{
			swap(*this, s);
			return *this;
		}

如果我们在string中添加如上两个函数,4、5两次赋值或拷贝会发生什么变化?
函数在返回tmp时,由于tmp出函数作用域就会调用析构函数销毁,所以会被识别成将亡值。将亡值属于右值,匹配 移动构造 和 移动赋值。
实际上过程还是和上面一模一样但是所有的拷贝构造都变成了移动构造,所有的赋值重载变成了移动赋值。
在这里插入图片描述

但是我们发现移动构造 和 移动拷贝 只是交换了资源,并没有开辟新的资源,将即将销毁的tmp中的内容与别的对象交换,从而实现资源的转移。比传统的深拷贝要节约了不少资源。

默认移动构造&&赋值

上面介绍了两个新的函数——移动构造函数 和 移动赋值函数,这两个也属于类的默认成员函数。

  • 默认移动构造
    生成条件

    1. 没有自己实现移动构造
    2. 没有实现析构函数、拷贝构造、拷贝赋值重载

    那么编译器会默认生成一个 ,对于内置类型会按字节拷贝,对于自定义类型会调用该类型的移动构造(如果存在的话,不存在的话就是拷贝构造)

  • 默认移动赋值
    生成条件

    1. 没有自己实现移动赋值
    2. 没有实现析构函数、拷贝构造、拷贝赋值重载

    那么编译器会默认生成一个 ,对于内置类型会按字节拷贝,对于自定义类型会调用该类型的移动赋值重载(如果存在的话,不存在的话就是普通赋值重载)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VSCode使用技巧,代码编写效率提升2倍以上!

VSCode是一款开源免费的跨平台文本编辑器&#xff0c;它的可扩展性和丰富的功能使得它成为了许多程序员的首选编辑器。在本文中&#xff0c;我将分享一些VSCode的使用技巧&#xff0c;帮助您更高效地使用它。 1. 插件 VSCode具有非常丰富的插件生态系统&#xff0c;通过安装插…

Python直接复制已有的venv虚拟环境以创建新的虚拟环境

Python venv创建的虚拟环境复制到其他路径&#xff0c;如何断开与原始虚拟环境的连接&#xff0c;成为一个全新的虚拟环境&#xff0c;且两个虚拟环境之间的更新互不影响&#xff1f;1.软件环境⚙️2.问题描述&#x1f50d;3.解决方法&#x1f421;3.1.方法1&#xff1a;先复制…

用Python Flask为女朋友做一个简单的网站(附可运行的源码)

&#x1f31f;所属专栏&#xff1a;献给榕榕&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该专栏系为女友准备的&#xff0c;里面会不定时发一些讨好她的技术作品&#xff0c;感兴趣的小伙伴可以关注一下~&#x1f449;文章简介…

什么是PCB走线的3W原则

在设计PCB的时候我们会经常说到3W原则&#xff0c; 它指的是两个PCB走线它们的中心间距不小于3倍线宽&#xff0c;这个W就是PCB走线的宽度。这样做的目的主要是为了减小走线1和走线2之间的串扰&#xff0c;一般对于时钟信号&#xff0c;复位信号等一些关键信号需要遵循3W原则。…

Vue插槽理解

Vue插槽理解插槽插槽 slot又名插槽&#xff0c;vue内容分发机制&#xff0c;组件内部的模板引擎使用slot元素作为承载分发内容的出口 插槽slot是子组件的一个模板标签元素&#xff0c;而这一个元素是否显示&#xff0c;以及怎么显示是由父组件决定的 slot分为三类&#xff1a;默…

链表带环问题(详解)

&#x1f506;链表带环问题&#xff08;详解&#xff09;&#x1f506;I 给定一个链表&#xff0c;判断链表中是否有环。&#x1f506;II 给定一个链表&#xff0c;返回链表开始入环的第一个结点。 如果链表无环&#xff0c;则返回 NULL。&#x1f506;复制带随机指针的链表&am…

集成方法!

目录 关注降低variance,选择bias较小的基学习器 Bagging Stacking Random Forest 关注降低bias,选择variance较小的基学习器 Adaboost Boosting 关注降低variance,选择bias较小的基学习器 Bagging 给定m个样本的数据集&#xff0c;利用有放回的随机采样法&#xff0c;得…

【Linux】操作系统(Operator System)

操作系统&#xff08;Operator System &#xff09;一、操作系统的概念二、操作系统的作用三、系统调用和库函数一、操作系统的概念 操作系统是一组控制和管理计算机软硬件资源&#xff0c;为用户提供便捷使用的计算机程序的集合&#xff0c;是配置在计算机硬件系统上的第一层…

模拟实现字符串有关函数(详细讲解)

在编写程序时&#xff0c;我们都喜欢写出简便并且效率高的代码&#xff0c;那么此时库函数中的有些函数就是我们的不二之选&#xff0c;那么&#xff0c;大家汇米你实现吗&#xff1f;下面就先从我们最简单的字符串函数说起&#xff1a; 1.strlen 这个是函数的格式&#xff0c…

做了个springboot接口参数解密的工具,我给它命名为万能钥匙(已上传maven中央仓库,附详细使用说明)

前言&#xff1a;之前工作中做过两个功能&#xff0c;就是之前写的这两篇博客&#xff0c;最近几天有个想法&#xff0c;给它做成一个springboot的start启动器&#xff0c;直接引入依赖&#xff0c;写好配置就能用了 springboot使用自定义注解实现接口参数解密&#xff0c;普通…

SpringSecurity学习(七)授权

授权 什么是权限管理 权限管理核心概念 SpringSecurity权限管理策略 基于URL地址的权限管理 基于方法的权限管理 一、权限管理 二、授权核心概念 在认证的过程成功之后会将当前用户登录信息保存到Authentication对象中&#xff0c;Authentication对象中有一个getAuthorities…

ChatGPT-4震撼发布

3月15日消息&#xff0c;美国当地时间周二&#xff0c;人工智能研究公司OpenAI发布了其下一代大型语言模型GPT-4&#xff0c;这是其支持ChatGPT和新必应等应用程序的最新AI大型语言模型。该公司表示&#xff0c;该模型在许多专业测试中的表现超出了“人类水平”。GPT-4, 相较于…

基于Java+Springboot+vue高校资源共享交流平台设计和实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

SpringBoot介绍。

目录 一、SpringBoot简介 1、SpringBoot开发步骤 2、官网构建工程 3、SpringBoot概述 二、配置文件 1、配置文件格式 2、yaml格式 3、yaml配置文件数据读取 三、多环境配置 1、yam文件 2、properties文件 3、命令行启动参数设置 四、SpringBoot整合 1、SpringBo…

界面开发(4)--- PyQt5实现打开图像及视频播放功能

PyQt5创建打开图像及播放视频页面 上篇文章主要介绍了如何实现登录界面的账号密码注册及登录功能&#xff0c;还简单介绍了有关数据库的连接方法。这篇文章我们介绍一下如何在设计的页面中打开本地的图像&#xff0c;以及实现视频播放功能。 实现打开图像功能 为了便于记录实…

OCPC系列 - OCPC介绍扫盲贴来啦

本文对oCPC做个介绍&#xff0c;它是一种智能投放模式&#xff0c;系统通过对广告主转化数据的对接和深度理解&#xff0c;实时预估每一次点击的转化率并基于竞争环境智能出价&#xff0c;通过强化高转化率曝光机会的获取&#xff0c;弱化低转化率曝光机会的展现&#xff0c;以…

力扣-进店却未进行过交易的顾客

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1581. 进店却未进行过交易的顾客二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行…

文心一言正式对标GPT-4,是青铜还是王者?

昨天&#xff0c;OpenAI正式发布GPT-4模型 号称史上最先进的AI系统 今天&#xff0c;百度文心一言在万众瞩目中闪亮登场 这款产品被视为中国版ChatGPT 在这一个多月内备受关注 文心一言某种程度上具有了对人类意图的理解能力 回答的准确性、逻辑性、流畅性都逐渐接近人类…

Go 微服务开发框架 DMicro 的设计思路

Go 微服务开发框架 DMicro 的设计思路 DMicro 源码地址: Gitee:dmicro: dmicro是一个高效、可扩展且简单易用的微服务框架。包含drpc,dserver等 背景 DMicro 诞生的背景&#xff0c;是因为我写了 10 来年的 PHP&#xff0c;想在公司内部推广 Go, 公司内部的组件及 rpc 协议都…

多模态特征融合:图像、语音、文本如何转为特征向量并进行分类

多模态特征融合前言输入层&#xff0c;数据集转为特征向量图像语音什么是时域信号&#xff0c;什么是频域信号语音信号转换 - 1.傅立叶变换语音信号转换 - 2.梅尔频率倒谱系数文本词袋模型词嵌入模型输出层&#xff0c;多模态模型合并前言 学习多模态的话题可以从深度学习的分…