【C++】引用和右值引用

news2025/1/24 22:44:44

目录

1. 引用

1.1 引用的概念

1.2 引用的特性

1.3 引用的使用场景

1.3.1 作为参数

1.3.2 作为返回值

1.4 常量引用

1.5 引用和指针的区别

2. 左值和右值

3. 右值引用

3.1 右值引用的概念

3.2 左值持久;右值短暂

3.3 变量是左值

3.4 标准库move函数


1. 引用

C++11中新增了一种引用:右值引用(rvalue reference),主要用于内置类。严格来说,当我们使用术语“引用(reference)”时,指的其实是“左值引用(lvalue reference)”。

1.1 引用的概念

引用(reference)不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

int a = 10;
int& ra = a;
//a为目标原名称,ra为目标引用名
//相当于给a取了一个别名叫ra
//a的地址和ra的地址相同

1.2 引用的特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
int a = 10;
int b = 20;

int& ra = a;//ok
int& ra;//err 引用在定义时必须初始化
int& c= a;//ok 一个变量可以有多个引用
int& ra = b;//err 引用一旦引用一个实体,再不能引用其他实体

1.3 引用的使用场景

1.3.1 作为参数

交换两个数的函数:

C语言版:修改变量,传变量的地址,再通过解引用,对变量进行操作

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

C++版:修改变量,传变量的引用,对引用进行操作,就是对变量进行操作

void Swap(int& r1, int& r2)
{
    int tmp = r1;
    r1 = r2;
    r2 = tmp;
}

1.3.2 作为返回值

返回一个值(非引用):存在拷贝

返回引用:

1. 减少拷贝

2. 调用者可以修改返回对象

//静态顺序表
#define N 10

typedef struct Array
{
	int a[N];
	int size;
}AY;

int& PosAt(AY& ay, int i)
{
	assert(i < N);
	return ay.a[i];
}

int main()
{
	AY ay;
	for (int i = 0; i < N; ++i)
	{
		PosAt(ay, i) = i * 10;//修改返回对象
	}
	for (int i = 0; i < N; ++i)
	{
		cout << PosAt(ay, i) << " ";
	}
	cout << endl;
	return 0;
}

如果出了函数作用域,对象还存在,则可以返回引用。

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

1.4 常量引用

const修饰的引用,称为常量引用或常引用。不能通过常量引用改变对应的对象的值。

const int ci = 10;
const int &r1 = ci;//ok r1是常量引用,对应的对象ci也是常量
r1 = 20;//err 不能通过常量引用改变对应的对象的值
int &r2 = ci;//err ci不能改变,当然也就不能通过引用去改变ci
             //假设合法,则可以通过r2来改变ci,这是不合法的

在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。

int i = 50;
const int& r1 = i;//ok 将const int&绑定到一个普通int对象上
const int& r2 = 10;//ok 将const int&绑定到一个int字面值上
const int& r3 = r1 * 2;//ok 将const int&绑定到一个表达式上
int& r4 = r1 * 2;//err

我们要清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:

double pi = 3.14;
const int& rpi = pi;//ok 将const int&绑定到一个普通double对象上

为什么rpi能够绑定pi?为了让rpi绑定一个整型对象,编译器把代码处理为:

const int temp = pi;//隐式类型转换 double->const int
const int& rpi = temp;//让rpi绑定这个临时量

所以当一个常量引用被绑定到另外一种类型上时,常量引用绑定的其实是相同类型的临时量。

如果函数的返回值是一个值而非引用,那么返回的是一个临时变量,且具有常量性。如果要用引用接收该值,只能用常量引用。

int Count()
{
	static int n = 0;
	n++;
	return n;
}

int& ret = Count();//err
const int& ret = Count();//ok

1.5 引用和指针的区别

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(4/8B)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

2. 左值和右值

C++的表达式要不然是右值(rvalue,读作"are-value"),要不然就是左值(lvalue,读作"ell-value")。这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。

在C++语言中,二者的区别就没那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但它们是右值而非左值。可以做一个简单的归纳:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。

不同的运算符对运算对象的要求各不相同,有的需要左值运算对象、有的需要右值运算对象;返回值也有差异,有的得到左值结果、有的得到右值结果。一个重要的原则(除右值引用)是在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。到目前为止,已经有几种我们熟悉的运算符是要用到左值的。

  • 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值。
  • 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
  • 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值。
  • 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本所得的结果也是左值。

3. 右值引用

3.1 右值引用的概念

为了支持移动操作,新标准引入了一种新的引用类型——右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。如我们将要看到的,右值引用有一个重要的性质——只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

左值和右值是表达式的属性。一些表达式生成或要求左值,而另外一些则生成或要求右值。一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。

类似任何引用,一个右值引用也不过是某个对象的另一个名字而已。如我们所知,对于常规引用(为了与右值引用区分开来,我们可以称之为左值引用(lvalue reference)),我们不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上:

int i = 42;
int &r = i;            //正确:r引用i
int &&rr = i;          //错误:不能将一个右值引用绑定到一个左值上
int &r2 = i * 42;      //错误:i*42是一个右值
const int &r3 = i * 42;//正确:我们可以将一个const的引用绑定到一个右值上
int &&rr2 = i * 42;    //正确:将rr2绑定到乘法结果上

返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都是返回左值的表达式的例子。我们可以将一个左值引用绑定到这类表达式的结果上。

返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。

3.2 左值持久;右值短暂

考察左值和右值表达式的列表,两者相互区别之处就很明显了:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

由于右值引用只能绑定到临时对象,我们得知

  • 所引用的对象将要被销毁
  • 该对象没有其他用户

这两个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

右值引用指向将要被销毁的对象。因此,我们可以从绑定到右值引用的对象“窃取”状态。

3.3 变量是左值

变量可以看作只有一个运算对象而没有运算符的表达式,虽然我们很少这样看待变量。类似其他任何表达式,变量表达式也有左值/右值属性。变量表达式都是左值。带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型的变量上,这有些令人惊讶:

int &&rr1 = 42; //正确:字面常量是右值
int &&rr2 = rr1;//错误:表达式rr1是左值!

其实有了右值表示临时对象这一观察结果,变量是左值这一特性并不令人惊讶。毕竟,变量是持久的,直至离开作用域时才被销毁。

变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。

3.4 标准库move函数

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。move函数返回给定对象的右值引用。

int &&rr3 = std::move(rr1);//ok

move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr1赋值或销毁它外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设。

我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

如前所述,与大多数标准库名字的使用不同,对move我们不提供using声明。我们直接调用std::move而不是move。

使用move的代码应该使用std::move而不是move。这样做可以避免潜在的名字冲突。

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

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

相关文章

docker使用与服务器上的可视化(ROS rviz等)

1.安装docker 安装docker&#xff1a;官网教程&#xff0c;按照官网命令一步步来即可。 添加当前用户到docker用户组&#xff1a; 【docker】添加用户到docker组&#xff0c;这样后面运行docker的时候前面不需要加sudo命令&#xff0c;否则运行docker的时候一直需要在前面加su…

开发人员必备的万能工具箱:He3

目录 1.简介2.安装3.详细功能清单4.常用快捷键5.使用示例5.1 测试正则表达式5.2 文本比较 He3官网&#xff1a; https://he3.app/zh/ 1.简介 今天&#xff0c;给大家推荐一款开发人员必备的万能工具箱&#xff1a;He3&#xff0c;这是一款免费的开发者工具箱&#xff0c;截至…

十分钟,零基础使用uniCloud完成后端管理系统搭建一

本文主要是通过uniCloud搭建后端管理系统&#xff0c;适合小白开发者、个人开发者&#xff0c;零后端基础&#xff0c;快速、低成本完成后端管理系统搭建。 还未创建uniCloud服务空间的开发者可以查看我的文章&#xff1a;Dcloud开发者注册&#xff0c;uniCloud服务空间创建。…

电脑显示屏不亮但是主机已开机?5种原因以及解决方案

电脑与我们的日常生活和工作密切相关&#xff0c;缺了它我们工作就很难正常展开。电脑使用久了&#xff0c;难免出现一些小问题&#xff0c;比如&#xff1a;电脑显示屏不亮但是主机已开机&#xff0c;这是什么原因造成的&#xff1f;我们应该怎么处理&#xff1f; 可能很多人…

随机数发生器设计(四)

随机数发生器设计&#xff08;四&#xff09;- DRNG 概述1 内部状态2 初始化函数3 SM3派生函数4 其他部分 概述 本示例DRNG设计参考了GM/T 0105 &#xff0c;基于SM3算法实现&#xff0c;内部功能接口包括初始化函数、重播种函数、输出函数和已知答案自测试函数&#xff0c;同…

正点原子STM32(基于HAL库)5

目录 SRAM 实验存储器简介SRAM 方案简介硬件设计程序设计程序流程图程序解析 下载验证 内存管理实验内存管理简介硬件设计程序设计程序流程图程序解析 下载验证 SD 卡实验SD 卡简介SD 物理结构命令和响应卡模式数据模式 SDIO 接口简介SDIO 主要功能及框图SDIO 的时钟SDIO 的命令…

《程序员必备品质》——沉稳1

目录 前言&#xff1a; 一.言论有分寸 1.1不抱怨不指责 1.2谈话时不触及别人的短 1.3学会装聋作哑 二.沉心静气 2.1先稳定情绪再解决问题 2.2急于求成则遇速不达 三.结尾 前言&#xff1a; 这周&#xff0c;我细细的读了一本书&#xff0c;还没精读完&#xff0c;不过…

华为OD机试真题 JavaScript 实现【最多几个直角三角形】【2023Q1 100分】

一、题目描述 有 N 条线段&#xff0c;长度分别为 a[1]-a[n]。 现要求你计算这 N 条线段最多可以组合成几个直角三角形&#xff0c;每条线段只能使用一次&#xff0c;每个三角形包含三条线段。 二、输入描述 第一行输入一个正整数 T (1< T< 100) &#xff0c;表示有…

开源WebRTC库放大器模式在采集桌面图像时遇到的DPI缩放与内存泄漏问题排查

目录 1、在非100%的显示比例下放大器采集到的桌面图像不全问题 1.1、通过manifest文件禁止系统对软件进行缩放 1.2、调用SetThreadDpiAwarenessContext函数&#xff0c;禁止系统对目标线程中的窗口进行缩放 1.3、使用winver命令查看Windows的年月版本 2、使用放大器模式遇…

4年经验去面试21k测试岗,看到这样的面试题我还是心虚了....

我是着急忙慌的准备简历——4年软件测试经验&#xff0c;可独立测试大型产品项目&#xff0c;熟悉项目测试流程...薪资要求&#xff1f;4年测试经验起码能要个21K吧 我加班肝了一页半简历&#xff0c;投出去一周&#xff0c;面试电话倒是不少&#xff0c;自信满满去面试&#…

Highcharts for Python crack

Highcharts for Python crack   Aligned the API with Highcharts Core v11.1. In particular, this includes:   Added AccessibilityPoint.description_format property.   Added support for .legend_symbol to plot options and series options.   Added .border_…

6月10日两练来了!

今天是2023年6月10日&#xff0c;每日两练来了&#xff01; 一。围城 题目 编程星球上有一片100*100的空地&#xff0c;啊哈沃德在此处建立了N座发电站&#xff0c;每座发电站占地位置1*1格。 为了防止源码巨人的攻击&#xff0c;啊哈沃德需要在所有发电站外围修建电网&#x…

redis第九章-Redis队列Stream、Redis6多线程详解

Redis队列Stream 前置说明&#xff1a;Redis5.0 最大的新特性就是多出了一个数据结构 Stream&#xff0c;它是一个新的强大的支持多播的可持久化的消息队列&#xff0c;作者声明 Redis Stream 地借鉴了 Kafka 的设计。 Redis Stream 的结构如上图所示,每一个Stream都有一个消息…

【浅谈DBA 最重要的素质---读书笔记】

&#x1f448;【上一篇】 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 【下一篇】&#x1f449; &#x1f53b;【来自DBA大佬的见解1】 对于一个准备进入 DBA 领域的人&#xff0c;我希望他勤奋、严谨、具有钻研精神及独立思考能力。…

Android应用程序进程的启动过程

Android应用程序进程的启动过程 导语 到这篇文章为止&#xff0c;我们已经简要地了解过了Android系统的启动流程了&#xff0c;其中比较重要的内容有Zygote进程的启动和SystemService以及Launcher的启动&#xff0c;接下来我们将要学习的是Android应用程序的启动过程&#xff…

Python爱好者的自我修养(1):简单输入与输出

Python简单输入与输出 1.输出1.1 简单输出1.2 转义字符1.2.1 定义1.2.2 常见的转义字符用法 2.输入3.温馨提示 终于…… 终于…… 我开始玩Python了 &#xff08;不是C不学了哈&#xff0c;C还是照更~&#xff09; 今天先来简单讲下输入和输出 1.输出 1.1 简单输出 输出的函…

【一篇让你学会】Web接口测试工具--Jmeter

关于Jmeter性能测试工具不再过多介绍。如果你要学习软件性能测试&#xff0c;那么多少应该会对它有所耳闻。 强烈建议阅读官方文档学习&#xff1a;http://jmeter.apache.org/index.html 还有比这个更权威更全面的介绍Jmeter工具使用的么&#xff1f; 不过&#xff0c;此处要介…

Win7批量执行Python文件

问题背景 平时都是用Pycharm跑代码&#xff0c;但是每次都需要在Configuration里修改Parameters&#xff0c;跑完一个才能重新修改跑下一个&#xff0c;很不方便&#xff0c;于是决定借助.bat文件实现批量执行。 困难一 电脑存在cmd闪退问题&#xff0c;之前一直逃避懒得解决…

ExtractOfficeContent: 提取Office文件中文本、表格和图像

引言 最近有空写了一下这个库&#xff0c;用来提取Office文件中的文本和图像内容&#xff0c;用作后续整理训练语料使用。最新更新请移步&#xff1a;Github Extract Office Content Use Installextract_office_content$ pip install extract_office_contentRun by CLI. Ext…

STM32——04-初识STM32单片机

什么是单片机&#xff1f; 单片机&#xff08; Single-Chip Microcomputer &#xff09;是一种集成电路芯片&#xff0c;把具有数据处理能力的中央处 理器 CPU 、随机存储器 RAM 、只读存储器 ROM 、多种 I/O 口和中断系统、定时器 / 计数器等功 能&#xff08;可能还包括显示驱…