左值、右值、左值引用、右值引用
1. 左值
左值(lvalue,left value),顾名思义就是赋值符号左边的值,可以取地址。准确来说,左值是表达式(不一定是赋值表达式)后依然存在的持久对象。
可以将左值看作是一个关联了名称的内存位置,允许程序的其他部分来访问它。在这里,我们将 "名称" 解释为任何可用于访问内存位置的表达式。所以,如果 arr 是一个数组,那么 arr[1] 和 *(arr+1) 都将被视为相同内存位置的“名称”。
2. 右值
右值(rvalue,right value),右边的值,是指表达式结束后就不再存在的临时对象,不能取地址。
纯右值(prvalue,pure rvalue),纯粹的右值,要么是纯粹的字面量,例如10, true;要么是求值结果相当于字面量或匿名临时对象,例如1+2。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda表达式都属于纯右值。
小结:相对而言,右值则是一个临时值,它不能被程序的其他部分访问。为了说明这些概念,请看以下程序段:
int square(int a)
{
return a * a;
}
int main()
{
int x = 0; // 1
x = 12; // 2
cout << x << endl; // 3
x = square(5); // 4
cout << x << endl; // 5
return 0;
}
在该程序中,x 是一个左值,这是因为 x 代表一个内存位置,它可以被程序的其他部分访问,例如上面注释的第 2、3、4 和 5 行。
而表达式 square(5) 却是一个右值,因为它代表了一个由编译器创建的临时内存位置,以保存由函数返回的值。该内存位置仅被访问一次,也就是在第 4 行赋值语句的右侧。在此之后,它就会立即被删除,再也不能被访问了。
对于包含右值的内存位置来说,其本质就是:它虽然没有名称,但是可以从程序的其他部分访问到它。
3. 左值引用
左值引用的声明是通过在某个类型后放置一个符号&来进行的。前文代码中的int & y = x;
便是一个左值引用。
需要注意的是,在定义左值引用时,=右边的要求是一个可修改的左值。因此下面几种左值引用都是错误的:
#include <stdio.h>
int main()
{
const int x = 5;
int y = 1;
int z = 1;
int & tmp1 = x; // ERROR:x不是一个可修改的左值
int & tmp2 = 5; // ERROR:5是一个右值
int & tmp3 = y + z; // ERROR:y+z是一个右值
return 0;
}
左值引用的用途:
(1) 作为复杂名称变量的别名
我们可以写出类似如下的语句:
auto & whichList = theList[myHash(x, theList.size())];
可以看到,我们用简短的whichList代替了其原本复杂的名称,这能够简化我们的代码书写。
(2) 用于rangeFor循环
设想我们希望通过rangeFor循环使一个vector对象所有值都增加1,下面的rangeFor循环是做不到的、
for (auto x : arr) // x仅相当于每个元素的拷贝
++x;
但我们可以通过使用引用达到这一目的
for (auto & x : arr)
++x;
(3) 避免复制大的对象
假定有一个findMax函数,它返回一个vector中最大的元素。若给定vector存储的是某些大的对象时,下述代码中的x拷贝返回的最大值到x的内存中:
auto x = finaMax(vector);
在大型的项目中这显然会增大程序的开销,这时我们可以通过引用来减小这类开销
auto & x = findMax(vector);
类似的,我们在处理函数返回值的时候也可以使用传引用返回。但是要注意,当返回的是类中私有属性时,传回的引用会导致外界能够对其修改。
(4) 参与函数中的参数传递
在C和C++的函数中,addSelf(int x)这类函数对直接传入的参数进行修改并不会改变原有参数的值。而有时我们希望能够实现类似swap(int a, int b)这类能够修改原参数的函数时,我们可以通过1.传入指针和2.传入引用实现。
swap函数的实现是一个很好的例子
#include <stdio.h>
void swap_non(int, int); // 直接传入参数
void swap_p(int *, int *); // 传入指针
void swap_r(int &, int &); // 传入引用
int main()
{
int a = 1;
int b = 2;
printf("直接传入参数时:\n");
swap_non(a, b);
printf("a = %d, b = %d\n", a, b);
printf("传入指针时:\n");
swap_p(&a, &b);
printf("a = %d, b = %d\n", a, b);
printf("传入引用时:\n");
swap_r(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
void swap_non(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
void swap_p(int * a, int * b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void swap_r(int & a, int & b)
{
int temp = a;
a = b;
b = temp;
}
4. 右值引用
首先要提出一个新的概念: 将亡值(xvalue,expiring value),是C++11为了引入右值引用而提出的概念(因此在传统 C++中,纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。
C++11 引入了右值引用的概念,以表示一个本应没有名称的临时对象。右值引用的声明与左值引用类似,但是它使用的是 2 个 & 符号(&&),以下代码使用了右值引用打印了两次 5 的平方:
int && rRef = square(5);
cout << rRef << endl;
cout << rRef << endl;
有意思的是,声明一个右值引用,给一个临时内存位置分配一个名称,这使得程序的其他部分访问该内存位置成为了可能,并且可以将这个临时位置变成一个左值。
右值引用不能约束到左值上,所以,以下代码将无法编译:
int x = 0;
int && rRefX = x;
再来看以下初始化语句:
int && rRef1 = square(5);
在初始化完成之后,这个包含值 square(5) 的内存位置有了一个名称,即 rRef1,所以 rRef1 本身变成了一个左值。这意味着后面的这个初始化语句将不会编译:
int && rRef2 = rRef1;
究其原因,就是右侧的 rRef1 不再是一个右值。综上所述,临时对象最多可以有一个左值引用指向它。如果函数有一个临时对象的左值引用,则可以确认,程序的其他部分都不能访问相同的对象。
右值引用会涉及到很多概念比如移动语义,完美转发,会在今后的文章中聊
引用和指针的区别
我们知道,指针是在内存中存放地址的一种变量,cpu能够直接通过而变量名访问唯一对应的内存单元,且每个内存单元的地址都是唯一的。
而变量名和引用,都可以看做内存的一个标签或是标识符,计算机通过是否符合标识符判断是否为目标内存,而一个内存可以有多个标识符
左值、右值、左值引用、右值引用相关推荐
- 左值引用——右值引用 详解
顾明思议 左值引用 就是对左值的引用 就是给左值取别名 右值引用 就是对右值的引用 就是给右值取别名 当改变别名是 该值也相应的改变 那么 何以区分哪些是左值哪些是右值呢? 左值 右值 在内存中有特定 ...
- 判断是否左值引用/右值引用
判断是否左值引用/右值引用 有时候搞不清楚推导出来的类型是左值引用还是右值引用,可以用接口辅助判断: int i = 0; std::is_lvalue_reference<decltype(+ ...
- C++11中的右值引用(对比左值引用和常引用)、移动构造函数和引用标识符
Hello!各位同学们大家好!逗比老师最近说起来还是挺尴尬的,为什么这么说呢?因为以前我对自己的C++水平还是相当自信的,经常以"精通"来自我评价.但是最近发现自己好像对C++11 ...
- 深入浅出C++左值引用,右值引用,移动语义。
什么是左值 右值? 简单来说左值就是可以取地址,在=左边的,而右值就是不可以取地址,在=右边的. int t=10; t可以通过&取地址在=左边 所以t是左值 10不可以取地址 在=右边10是 ...
- c++ | 左值引用 右值引用
左值:可以取地址的.有名字的就是左值,比如 int a; 右值:不能取地址的是右值,表达式结束后就会被销毁, 比如 a*3 左值引用:就是普通引用,是为对象起的别名,必须被初始化,与变量绑定到一起 如 ...
- [c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发
1.左值和右值 1.1左值和右值定义 在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置.一般来说,右值是暂时和短暂的,而 ...
- 左值/右值/左值引用/右值引用/move的用法介绍
目录 问题 左值和右值 概念总结: 需要用到左值的运算符: 引用分类 左值引用 右值引用 右值引用到底什么用? std::move()函数介绍 问题 什么是左值和右值? 什么是左/右值引用? 左/右值 ...
- std::move 左值右值 左值引用右值引用
参考:https://blog.csdn.net/daaikuaichuan/article/details/88371948 https://zhuanlan.zhihu.com/p/9458820 ...
- 笛卡尔树 (25 分)笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2。首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大。其次所有结点的K2关键字
立志用最少的代码做最高效的表达 笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2.首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大.其次所有结点的 ...
- 2020-09-22C++学习笔记之引用1(1.引用(普通引用)2.引用做函数参数 3.引用的意义 4.引用本质5.引用结论 6.函数返回值是引用(引用当左值)7测试代码)
2020-09-22C++学习笔记之引用1(1.引用(普通引用)2.引用做函数参数 3.引用的意义 4.引用本质5.引用结论 6.函数返回值是引用(引用当左值)7测试代码) 1.引用(普通引用) 变量 ...
最新文章
- R语言-路径设置与工作目录修改
- 俄罗斯拟明年在36万台华为平板安装“极光”操作系统
- 学习笔记------tag文件
- 简单入门循环神经网络RNN:时间序列数据的首选神经网络
- 逻辑回归阈值_逻辑回归or线性回归,傻傻分不清楚
- 功率增长步长(powerRampingStep)
- 2020 云原生技术 7 大领域趋势全预测
- 牛客网暑期ACM多校训练营(第五场)A-gap (二分答案)
- GDB 调试多进程或者多线程应用
- 牛客多校三 B Black and white
- boost::io::ostream_put用法的测试程序
- java lambda例子_Java lambda 表达式常用示例
- Transactional cannot be resolved to a type
- java操作ssdb:set、map、list..
- 矩阵乘法(信息学奥赛一本通-T1125)
- HttpHelper使用记录
- PHP数据库扩展 - PDO操作
- 长虹电视刷机固件包汇总
- 基于Matlab人脸识别(PCA算法)
- 汉字转拼音(支持多音字)
热门文章
- reac——父组件向子组件传递值,子组件何时能同步获得父组件改变后的值
- 直播技术初体验,简单实现直播不同阶段
- android学汇资料总整理
- oracle参数文件initorcl位置,oracle 参数文件详解
- 核心概念——节点/边/Combo——内置节点——Triangle
- BASIC-4 数列特征
- 机器学习 —— python库 —— 使用array创建
- python人工智能——机器学习——转换器与估计器
- 【机器视觉】 dev_map_var算子
- 【Protocol Buffer】Protocol Buffer入门教程(六):枚举和包