C++中的左值和右值

目录

一. 左值和右值的概念

1. 左值

1.1 可修改的的左值

1.2 不可修改的左值

右值

二. 左值引用和右值引用

1. 左值引用

2. 右值引用

主要用途

          1. 移动语义

          2. 完美转发

             2.1 引用折叠

             2.2 std::forward


一. 左值和右值的概念

什么是左值和右值

1. 左值

        左值是一个表示数据的表达式,它代表一个具名的内存位置,程序可以获取其地址,可以通过地址访问它们,是可被引用的数据对象。左值又可分为可修改的的左值和不可修改的左值。

1.1 可修改的的左值

一开始,左值其实就是指可出现在赋值语句左边的表达式,它们的值是可以被修改的,当然这是最初的概念了。比如常规变量名、解除引用的指针、数组元素、引用、对象成员都是左值。

	int a;
	a = 10;

	int* ptr = &a;
	*ptr = 100;

	vector<int> vec(5,10);
	vec[2] = 20;

	int& c = a;
	c = 1000;

1.2 不可修改的左值

后面随着关键字const的引入,这个左值的概念发生了变化 。因为虽然不能对const变量赋值,但是可以获取其地址。

	const int a = 10;
	const int* ptrA = &a; //可以通过地址访问它
	a = 100;              //错误

  • 右值

       前面说完了左值,那右值是什么呢?通过排他性来定义的话,每个表达式不是左值就是右值。 右值代表一个临时的值,不能被取地址,不能被修改,是不能出现在赋值语句的左边的。

       右值包括字面常量(C-style字符串除外,它表示地址)、临时对象、诸如x+y等表达式、返回值的函数(条件是该函数返回的不是引用)等。

int foo() {
	return 1;
}

void test01() {

	int a = 1;
    int b = 2;
	int c = a + b;
    
	int d = foo();
};

       比如上面的a,b,c,d这几个变量都是左值,但是a+b和foo()都是右值。 我们知道是无法写成如下这样的:

    a + b = 3;
    
	foo() = 2;

二. 左值引用和右值引用

1. 左值引用

左值引用也就是传统的C++引用,通过&符号来声明,它可以使得标识符关联到左值。引用是已定义变量的别名,例如下面将b作为a变量的别名,b和a指向相同的值和内存单元

	int a = 1;
    int& b = a;

引用必须在创建时就要进行初始化,一旦与某个变量关联起来,就将一直相关联,不会中途关联到其他变量

	int a = 1;
    //错误,因为引用必须在创建时就关联变量
    int& b;
	b = a; 

引用的主要用途是用来作为函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

2. 右值引用

在C++11引入了右值引用(rvalue reference)的概念,通过&&符号来声明,右值引用可以关联到右值。

	int && a = 1;
	cout << "a = " << a << ", " << &a<< endl;

  • 主要用途

引入右值引用的目的是什么呢?其主要目的之一是实现移动语义。

          1. 移动语义

移动语义是一种优化技术,它允在对象之间转移资源的所有权,而不是进行资源的复制或者拷贝。移动语义这种技术正是通过使用右值引用来实现的。

先说一下为什么会诞生移动语义这个东西:当调用拷贝构造函数或赋值运算符时会对对象进行拷贝操作,也就是会申请一块新的内存空间,然后把数据复制到新的内存空间当中,但是这对于大型对象来说工作量是很大的,当然在一些情况下这是必要的操作,但是在某些场景下,可能其实不用额外拷贝一份,只需要将对象的资源所有权从一个对象转移到另一个对象就可以满足要求了。举一个例子(取自于C++ Primer Plus):

vector<string> allcaps(const vector<string>& vs) {
	vector<string> temp;
	//code that store an all-uppercase version of vs in temp(将vs转换成全大写版本存储在temp中)
	return temp;
}

vector<string> vstr;
//build up a vecor of 20000 strings,each of 1000 characters(构建存储有20000个字符串的vector,每个字符串包含1000个字符)

vector<string> vstr_copy(allcaps<vstr>);

可以发现,allocaps()创建了对象temp,这个temp对象管理着20000000个字符;vector和string的拷贝构造函数创建这20000000字符的副本,然后程序再删除allocaps()返回的临时对象temp。这里面做了大量的无用功,这里先是将temp对象的值拷贝给vstr_copy,然后再删除temp对象;如果能够直接将temp对数据的所有权转让给vstr_copy也就是不将temp所管理的20000000个字符复制到新地方,而是保留在原来的地方,并将vstr_copy与之相关联,这样的话,工作量就省掉了很多,效率也就上来了。于是移动语义这个技术就出现了,它便是可以将对象的资源所有权从一个对象转移到另一个对象,而不进行资源的复制的技术。

移动语义得以实现,这其中便离不开右值引用的支持。那右值引用对移动语义的支持体现在什么地方呢:因为需要让编译器知道什么时候需要的是拷贝,什么时候不需要,这就是右值引用发挥作用的地方了。简单来说,就是编译器会根据会根据传进来的参数是左值引用还是右值引用来决定调用拷贝构造函数还是移动构造函数(移动构造函数里将资源从一个对象转移到另一个对象,而不用复制)。

class test {
public:
test(const test& t); //拷贝构造函数
test(test&& t);      //移动构造函数

private:
	int n;
};

总结,移动语义发生,需要两个步骤:

1. 右值引用让编译器知道何时可使用移动语义

2. 编译移动构造函数,使其提供所需的方法。也就是怎么让资源从一个对象转移到另一个对象,而不用复制

          2. 完美转发

完美转发是C++11引入的一种特性,它是指泛型编程(模板编程)中,函数模板能够完全自己模板参数的类型传递给内部调用的其他函数,即参数左右值的属性不会发生变化。

 完美转发能够保留参数的类型和值类别(左值或右值),从而实现更为高效且准确地传递参数,用于解决在函数模板中传递参数时的类型推导问题。它允许将参数以原始的形式传递给另一个函数,而不会引入额外的拷贝或移动操作。

完美转发的实现过程中有借助右值引用和std::forward。

             2.1 引用折叠

C++中,一般右值引用的参数只能接收右值,不能接收左值;但是在函数模板中使用右值引用的参数又不太一样,它既可以接收右值引用,也可以接收左值引用。借助C++的引用折叠规则,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。

下面这个例子里,虽然模板函数f的参数t声明为了右值引用,但是实际传参是时可以是左值引用。这里“&&”又可成为通用引用

template<class T>
void f(T&& t) {
	g(t);
};

假设用 A 表示实际传递参数的类型:

  • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&)
  • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)

引用折叠规则:

& + & -> &
& + && -> &
&& + & -> &
&& + && -> &&

可以发现,一旦定义中出现了左值引用,引用折叠规则总是优先将其折叠为左值引用。

             2.2 std::forward

std::forward是一个模板函数,用于在函数模板中将参数原封不动地转发给其他函数,保持参数的值类别(左值引用或右值引用的属性)和const/volatile限定符。

以前面那个例子,在函数模板f里调用函数g时,没有加forward,可以发现调用f时,即使传进去的参数是右值引用,但是从运行结果来看,f里调用函数g()时,传给函数g()的却变成了左值引用,这是因为传入给g()的是一个具名变量参数,具名变量即使被声明为右值类型也不会被当作右值,g()会认为这个值就是是一个左值;参数是左值,使用这个参数时还是会调用拷贝构造函数(如果是class的话)。

void g(int& a) {
	cout << "左值引用" << endl;
}

void g(int&& a) {
	cout << "右值引用" << endl;
}

template<typename T>
void f(T&& t) {
	g(t);
};

void test01() {
    f(2);    //传入右值引用
	return;
};

int main() {
	test01();
	return 0;
}

如果在调用函数g()时,改写成g(forward<T>(t))后,可以看到运行结果变成了预期的“右值引用”,这便是forward提供的完美转发的功劳。

void g(int& a) {
	cout << "左值引用" << endl;
}

void g(int&& a) {
	cout << "右值引用" << endl;
}

template<typename T>
void f(T&& t) {
	g(forward<T>(t));
};

void test01() {
    f(2);    //传入右值引用
	return;
};

参数t是一个通用引用,它可以接受左值引用或者左值引用的参数。通过使用std::forward,可以将原参数t封不动地转发给函数g,并保持参数t的左值引用或者左值引用属性不会发生变化。

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

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

相关文章

Unity3D 使用 Proto

一. 下载与安装 这里下载Google Protobuff下载 1. 源码用来编译CSharp 相关配置 2. win64 用于编译 proto 文件 二. 编译 1. 使用VS 打开 2. 点击最上面菜单栏 工具>NuGet 包管理器>管理解决方案的NuGet 管理包 版本一定要选择咱们一开始下载的对应版本否则不兼容&am…

使用免费的L53巧解Freenom域名失效问题

进入2月份以来&#xff0c;不少小伙伴纷纷收到Freenom提供的域名失效&#xff0c;状态由正常变成了Pending。 失效后&#xff0c;域名无法使用&#xff0c;免费的午餐没有了&#xff0c;而现在域名的价格也是水涨船高&#xff0c;真是XXX。很多做外贸的小伙伴表示 难 啊&#x…

树状数组与线段树<2>——线段树初步

这个系列终于更新了(主要因为树状数组初步比较成功) 话不多说&#xff0c;切入正题。 什么是线段树&#xff1f; 线段树是一种支持单点修改区间查询(树状数组也行) and 区间修改单点查询(树状数组不行) and 区间修改区间查询(树状数组更不行)的高级数据结构&#xff0c;相当…

Chiplet技术与汽车芯片(二)

目录 1.回顾 2.Chiplet的优势 2.1 提升芯片良率、降本增效 2.2 设计灵活&#xff0c;降低设计成本 2.3 标准实行&#xff0c;构建生态 3.Chiplet如何上车 1.回顾 上一篇&#xff0c;我们将来芯粒到底是什么东西&#xff0c;本篇我们来看芯粒技术的优势&#xff0c;以及它…

5.1 Ajax数据爬取之初介绍

目录 1. Ajax 数据介绍 2. Ajax 分析 2.1 Ajax 例子 2.2 Ajax 分析方法 &#xff08;1&#xff09;在网页页面右键&#xff0c;检查 &#xff08;2&#xff09;找到network&#xff0c;ctrl R刷新 &#xff08;3&#xff09;找 Ajax 数据包 &#xff08;4&#xff09;…

多线程相关(4)

线程安全-下 使用层面锁优化减少锁的时间&#xff1a;减少锁的粒度&#xff1a;锁粗化&#xff1a;使用读写锁&#xff1a;使用CAS&#xff1a; 系统层面锁优化自适应自旋锁锁消除锁升级偏向锁轻量级锁重量级锁 ThreadLocal原理ThreadLocal简介原理ThreadLocal内存泄漏 HashMap…

VMware使用虚拟机,开启时报错:无法连接虚拟设备 0:0,因为主机上没有相应的设备。——解决方法

检查虚拟机配置文件并确保物理设备已正确连接。 操作&#xff1a; 选中虚拟机&#xff0c;打开设置&#xff0c;点击CD/DVD。在连接处选择使用ISO镜像文件

fpga_硬件加速引擎

一 什么是硬件加速引擎 硬件加速引擎&#xff0c;也称硬件加速器&#xff0c;是一种采用专用加速芯片/模块替代cpu完成复杂耗时的大算力操作&#xff0c;其过程不需要或者仅需要少量cpu参与。 二 典型的硬件加速引擎 典型的硬件加速引擎有GPU&#xff0c;DSP&#xff0c;ISP&a…

【二分查找】【浮点数的二分查找】【二分答案查找】

文章目录 前言一、二分查找&#xff08;Binary Search&#xff09;二、浮点数的二分查找三、二分答案总结 前言 今天记录一下基础算法之二分查找 一、二分查找&#xff08;Binary Search&#xff09; 二分查找&#xff08;Binary Search&#xff09;是一种在有序数组中查找目…

1 Nacos数据持久化方式

Nacos 支持两种数据持久化方式&#xff0c;一种是利用内置的数据库&#xff0c;另一种是利用外置的数据源。 1、内置数据库支持 Nacos 默认内置了一些数据存储解决方案&#xff0c;如内嵌的 Derby 数据库。 这种内置方式主要用于轻量级或测试环境。 2、外置数据库支持 对于生…

【RN】学习使用 Reactive Native内置UI组件

简言 当把导航处理好后&#xff0c;就可以学习使用ui组件了&#xff08;两者没有先后关系&#xff0c;个人习惯&#xff09;。 在 Android 和 iOS 开发中&#xff0c;一个视图是 UI 的基本组成部分&#xff1a;屏幕上的一个小矩形元素、可用于显示文本、图像或响应用户输入。甚…

如何使用逻辑回归处理多标签问题?

逻辑回归处理多分类 1、背景描述2、One vs One3、One vs Rest4、从Sigmoid到Softmax的推导 1、背景描述 逻辑回归本身只能用于二分类问题&#xff0c;如果实际情况是多分类的&#xff0c;那么就需要对模型进行一些改动。下面介绍三种常用的将逻辑回归用于多分类的方法 2、One …

目标跟踪之KCF详解

High-Speed Tracking with Kernelized Correlation Filters 使用内核化相关滤波器进行高速跟踪 大多数现代跟踪器的核心组件是判别分类器&#xff0c;其任务是区分目标和周围环境。为了应对自然图像变化&#xff0c;此分类器通常使用平移和缩放的样本补丁进行训练。此类样本集…

用Python实现创建十二星座数据分析图表

下面小编提供的代码中&#xff0c;您已经将pie.render()注释掉&#xff0c;并使用了pie.render_to_file(十二星座.svg)来将饼状图渲染到一个名为十二星座.svg的文件中。这是一个正确的做法&#xff0c;如果您想在文件中保存图表而不是在浏览器中显示它。 成功创建图表&#xf…

嵌入式软件分层设计的思想分析

“嵌入式开发&#xff0c;点灯一路发” 那今天我们就以控制LED闪烁为例&#xff0c;来聊聊嵌入式软件分层: ——————————— | | | P1.1 |-----I<|--------------<| | | | P2.1 |-------------/ ---------…

MyBatis的⾼级映射及延迟加载

MyBatis的⾼级映射及延迟加载 一、多对一1.方式一&#xff1a;级联属性映射2.方式二&#xff1a;association3.方式三&#xff1a;分步查询 二、一对多1.方式一&#xff1a;collection2.方式二&#xff1a;分步查询 三、延迟加载&#xff08;懒加载&#xff09;1.分步查询的优点…

【C++】类和对象之拷贝构造函数篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 传值传参和传引用传参3. 概念4. 特征 1. 前言 在前面学习了6个默认成员函数中的构造函数和析构函数 【C】构造函数和析构函数详解&#xff0c;接下来继续往后看拷…

uvloop,一个强大的 Python 异步IO编程库!

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站零基础入门的AI学习网站~。 目录 ​编辑 前言 什么是uvloop库&#xff1f; 安装uvloop库 使用uvloop库 uvloop库的功能特性 1. 更…

nodejs+vue+ElementUi废品废弃资源回收系统

系统主要是以后台管理员管理为主。管理员需要先登录系统然后才可以使用本系统&#xff0c;管理员可以对系统用户管理、用户信息管理、回收站点管理、站点分类管理、站点分类管理、留言板管理、系统管理进行添加、查询、修改、删除&#xff0c;以保障废弃资源回收系统系统的正常…

【Linux】部署单机项目(自动化启动)

目录 一.jdk安装 二.tomcat安装 三.MySQL安装 四.部署项目 一.jdk安装 1.上传jdk安装包 jdk-8u151-linux-x64.tar.gz 进入opt目录&#xff0c;将安装包拖进去 2.解压安装包 防止后面单个系列解压操作&#xff0c;我这边就直接将所有的要用的全部给解压&#xff0c;如下图注…