C++中左值(引用)及右值(引用)详解
- 写C++代码编译时,有时会出现左值问题错误或右值错误,那左值和右值究竟是什么呢???
一、左值与右值
啥是左值和右值呢?
左值:在内存有确定存储地址、有变量名,表达式结束依然存在的值,简单来说左值就是非临时对象。
右值:就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值,简单来说右值就是临时对象。
int a = 0; // 在这条语句中,a 是左值,0 是临时值,就是右值。
左值可以分为两类:非常量左值和常量左值;
int a=10; // a 为非常量左值(有确定存储地址,也有变量名) const int a1=10; //a1 为常量左值(有确定存储地址,也有变量名) const int a2=20; //a2 为常量左值(有确定存储地址,也有变量名)
同理,右值也可以分为两类:非常量右值和常量右值。
int a=10; // 10 为非常量右值 const int a1=10; const int a2=20; a1+a2 // (a1+a2) 为常量右值
二、左值引用于右值引用
知道了左值与右值了,那啥是左值引用与右值引用呢?
左值引用:其实就是绑定到左值的引用,通过&来获得左值引用。
- 左值引用举例:
int a=10; //非常量左值(有确定存储地址,也有变量名) const int a1=10; //常量左值(有确定存储地址,也有变量名) const int a2=20; //常量左值(有确定存储地址,也有变量名)//非常量左值引用 int &b1=a; //正确,a是一个非常量左值,可以被非常量左值引用绑定 int &b2=a1; //错误,a1是一个常量左值,不可以被非常量左值引用绑定 int &b3=10; //错误,10是一个非常量右值,不可以被非常量左值引用绑定 int &b4=a1+a2; //错误,(a1+a2)是一个常量右值,不可以被非常量左值引用绑定//常量左值引用 const int &c1=a; //正确,a是一个非常量左值,可以被非常量右值引用绑定 const int &c2=a1; //正确,a1是一个常量左值,可以被非常量右值引用绑定 const int &c3=a+a1; //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定 const int &c4=a1+a2; //正确,(a1+a2)是一个常量右值,可以被非常量右值引用绑定
- 总结归纳:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。
右值引用:其实也是绑定到右值的引用,通过&&来获得右值引用。
- 左值引用举例:
int a=10; //非常量左值(有确定存储地址,也有变量名) const int a1=20; //常量左值(有确定存储地址,也有变量名) const int a2=20; //常量左值(有确定存储地址,也有变量名)//非常量右值引用 int &&b1=a; //错误,a是一个非常量左值,不可以被非常量右值引用绑定 int &&b2=a1; //错误,a1是一个常量左值,不可以被非常量右值引用绑定 int &&b3=10; //正确,10是一个非常量右值,可以被非常量右值引用绑定 int &&b4=a1+a2; //错误,(a1+a2)是一个常量右值,不可以被非常量右值引用绑定//常量右值引用 const int &&c1=a; //错误,a是一个非常量左值,不可以被常量右值引用绑定 const int &&c2=a1; //错误,a1是一个常量左值,不可以被常量右值引用绑定 const int &&c3=a+a1; //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定 const int &&c4=a1+a2; //正确,(a1+a2)是一个常量右值,不可以被常量右值引用绑定
- 总结归纳:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。
从上述可以发现,常量左值引用可以绑定到右值上,但右值引用不能绑定任何类型的左值,若想利用右值引用绑定左值该怎么办呢?
C++11中提供了一个标准库move函数获得绑定到左值上的右值引用,即直接调用std::move告诉编译器将左值像对待同类型右值一样处理,但是被调用后的左值将不能再被使用。
- std::move()函数举例
int a=10; //非常量左值(有确定存储地址,也有变量名) const int a1=20; //常量左值(有确定存储地址,也有变量名)//非常量右值引用 int &&d1=std::move(a); //正确,将非常量左值a转换为非常量右值,可以被非常量右值引用绑定 int &&d2=std::move(a1); //错误,将常量左值a1转换为常量右值,不可以被非常量右值引用绑定//常量右值引用 const int &&c1=std::move(a); //正确,将非常量左值a转换为非常量右值,可以被常量右值引用绑定 const int &&c2=std::move(a1); //正确,将常量左值a1转换为常量右值,可以被常量右值引用绑定
最后可以发现,编译器利用std::move将左值强制转换为相同类型的右值之后,引用情况跟右值是一模一样的。
三、右值引用与左值引用的区别
1、左值引用绑定到有确定存储空间以及变量名的对象上,表达式结束后对象依然存在;
2、右值引用绑定到要求转换的表达式、字面常量、返回右值的表达式等临时对象上,赋值表达式结束后就对象就会被销毁。
3、左值引用后可以利用别名修改左值对象;右值引用绑定的值不能修改。
四、引入右值引用的原因
- 1、替代需要销毁对象的拷贝,提高效率:某些情况下,需要拷贝一个对象然后将其销毁,如:临时类对象的拷贝就要先将旧内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,就可以让新对象直接使用旧内存并且销毁原对象,这样就减少了内存和运算资源的使用,从而提高了运行效率;
- 2、移动含有不能共享资源的类对象:像IO、unique_ptr这样的类包含不能被共享的资源(如:IO缓冲、指针),因此,这些类对象不能拷贝但可以移动。这种情况,需要先调用std::move将左值强制转换为右值,再进行右值引用。
在这举例里用一下别的文章里的代码来说明一下引入右值引用的好处:如下原代码的文章在该链接点击
有如下string类,实现了拷贝构造函数和赋值运算符重载。
class MyString {private:char* _data;size_t _len;void _init_data(const char *s) {_data = new char[_len + 1];memcpy(_data, s, _len);_data[_len] = '\0';}
public:MyString() {_data = NULL;_len = 0;}MyString(const char* p) {_len = strlen(p);_init_data(p);}MyString(const MyString& str) {_len = str._len;_init_data(str._data);std::cout << "Copy Constructor is called! source: " << str._data << std::endl;}MyString& operator=(const MyString& str) {if (this != &str) {_len = str._len;_init_data(str._data);}std::cout << "Copy Assignment is called! source: " << str._data << std::endl;return *this;}virtual ~MyString() {if (_data != NULL) {std::cout << "Destructor is called! " << std::endl; free(_data);}}
};int main() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World"));
}
运行结果:
Copy Assignment is called! source: Hello
Destructor is called!
Copy Constructor is called! source: World
Destructor is called!
Destructor is called!
Destructor is called!
总共执行了2次拷贝,MyString(“Hello”)和MyString(“World”)都是临时对象,临时对象被使用完之后会被立即析构,在析构函数中free掉申请的内存资源。 如果能够直接使用临时对象已经申请的资源,并在其析构函数中取消对资源的释放,这样既能节省资源,有能节省资源申请和释放的时间。 这正是定义转移语义的目的。
通过加入定义转移构造函数和转移赋值操作符重载来实现右值引用(即复用临时对象):
MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL; // ! 防止在析构函数中将内存释放掉}MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; str._len = 0; str._data = NULL; // ! 防止在析构函数中将内存释放掉} return *this; }
运行结果:
Move Assignment is called! source: Hello
Move Constructor is called! source: World
Destructor is called!
Destructor is called!
需要注意的是:右值引用并不能阻止编译器在临时对象使用完之后将其释放掉的事实,所以转移构造函数和转移赋值操作符重载函数 中都将_data赋值为了NULL,而且析构函数中保证了_data != NULL才会释放。
若发现该文章上有错处的地方可在下方留言告知一下,十分感谢!
C++中左值(引用)及右值(引用)详解相关推荐
- C++/C++11中左值、左值引用、右值、右值引用的使用
C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...
- c 表达式必须是可修改的左值_C++中的左值,右值,左值引用,右值引用
童帅 2020-2-22 文中的"表达式"都是指赋值表达式 左值,右值,左值引用,右值引用 到底是什么 左值和右值 int a = 10; int b = 5; int c = a ...
- 39.左值、左值引用、右值、右值引用
1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体: 右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以在CPU寄存器. ...
- 左值和左值引用、右值和右值引用
左值和左值引用.右值和右值引用 - _yanghh - 博客园 (cnblogs.com)
- 的引用_左值、右值、左值引用、右值引用
[导读]:本文主要详细介绍了左值.右值.左值引用.右值引用以及move.完美转发. 左值和右值 左值(left-values),缩写:lvalues 右值(right-values),缩写:rvalu ...
- 【C++11】左值引用和右值引用
目录 一.新的类功能 1.新的默认成员函数 2.类成员变量初始化 3.强制生成默认函数的关键字default 4.禁止生成默认函数的关键字delete 二.左值和右值 1.左值和左值引用 2.右值和右 ...
- C++ 左值引用和右值引用
转自叶余的知乎文章,如果侵权请告知删除! https://zhuanlan.zhihu.com/p/97128024 左值引用 先看一下传统的左值引用. int a = 10; int &b ...
- 左值、左值引用、右值、右值引用
关于左值 右值示例 可看这里 1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体: 右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以 ...
- C++11:搞清楚万能引用和右值引用
前言 我们通过一个问题来进入今天的话题: 1.形如 "type&&" 的结构,就是右值引用吗? 2.以下哪些属于右值引用? ① void fun(Widget &a ...
- (P36-P39)右值和右值引用、右值引用的作用以及使用、未定引用类型的推导、右值引用的传递
文章目录 1.右值 2. 右值引用 3.性能优化 4.&& 的特性 5.右值引用的传递 1.右值 C++11 增加了一个新的类型,称为右值引用( R-value reference), ...
最新文章
- 用Enterprise Architect从源码自动生成类图
- C++继承中的访问级别
- 互斥事件的概念和公式_IGCSE数学5月大考冲刺A*?必备公式与技巧
- java poi读取word中附件_Java POI导入word, 带图片
- Redis 写磁盘出错 Cannot allocate memory
- Jconsole工具和Java VisualVM
- IEEE ISO/IEC简介
- 从零开始学游戏编程——可视化编程游戏开发工具学习指南
- 计算机管理中打开移动硬盘磁盘必须格式化,Win10下移动硬盘无法打开提示需要格式化的三种解决方法...
- 全面解读流程图|附共享单车摩拜ofo案例分析
- Android App瘦身新姿势——Android App Bundle
- 【历史上的今天】1 月 18 日:微软的“技术布道者”;反盗版法案抗议行动;哈佛 Mark I 灵感起源
- (BAT批处理)批量文件夹重命名,要求是在原文件夹名前加上英文字母前缀aa
- php 通过当前时间计算几天,几周,几个月或者几年以后的时间
- ARFoundation从零开始3-创建ARFoundation项目
- STM32F103ZET6【标准库函数开发】------PB3,PB4当做普通IO口,重定义
- kubernetes源码剖析读后感(一)
- Prior 、Posterior 和 Likelihood 的理解与几种表达方式
- OpenCV-判断OpenCV摄像头是否断开
- Linux学习笔记41——什么是 daemon 与服务 (service)
热门文章
- JAVA猜数字小游戏(随机数Random类)
- 50个多色渐变下载分享
- 直播系统源码App中Android酷炫礼物动画直播平台源码搭建教程(上篇)
- 服务器断电后自动重启
- 南京集成电路大学,到底是个什么大学?
- PRN(20210426):GRAPH-BASED CONTINUAL LEARNING(ICLR2021)
- Python~无法初始化设备 PRN
- 华为公关事故处罚流出;FF中国分家细节曝光;苹果股价下跌拖累美股|雷锋早报...
- 标准差分进化算法(DE)
- MATLAB图像处理简单程序(1)—实现几何、算数简单变换,滤镜处理以及图片变换效果展示