左值、右值、左值引用、右值引用与move语义 Lvalue Rvalue Lref Rref and Move
左值和右值
左值
字面意思即能用在赋值语句等号左侧的内容(它代表一个地址)
右值
不能作为左值的值,右值不能出现在赋值语句等号的左侧
分辨
C++中的一条表达式,要么是左值,要么是右值,不可能两者都是。
i = i+1
- i是左值,但i也出现在赋值语句等号右侧,此时,用的是i对象的值,称这个对象i有一种右值属性。
- 而i出现在赋值语句等号左侧时,用的时i对象在内存中的地址,此时称这个对象i有一种左值属性。
一个左值可能同时具有左值属性和右值属性
需要用到左值的运算符
- 赋值运算符=
- 取地址运算符&
- string、vector的小标运算符[]等,迭代器的递增、递减运算符
左值代表一个地址,所以左值表达式求值的结果就得是一个对象,得有地址,而100这个数字显然是右值
引用分类
- 左值引用(绑定到左值),引用那些希望改变值的对象,左值引用带有一个&
- const引用(常量引用),也是左值引用,引用那些不希望改变值的对象
- 右值引用(绑定到右值),右值引用侧重表达所引用对象的值在使用之后就无须保留了(如临时变量)。右值引用带两个&&
左值引用
左值代表一个变量、一个地址,而左值引用则是指向对象的一个别名
右值引用
右值引用必须绑定到右值的引用,主要用来绑定那些“即将销毁/临时的对象”
int&& a = 3;
典型例子分析
++i // 先加后用
i++ // 先用后加
- 为什么++i是左值表达式呢?因为++i是直接给i加1,然后返回i本身,因为i是变量,所以可以被赋值,因此是左值表达式。
- 为什么i++是右值表达式呢?因为i++先产生一个临时变量来保存i的值用于使用,再给i加1,接着返回临时变量,之后系统再释放这个临时变量,临时变量释放了不能再被赋值,因此是右值表达式。
int i = 1;
int&& r1 = i++; // 可以成功绑定右值,但此后r1的值和i的值没有关系
int& r2 = i++; // 不可以
int& r3 = ++i; // 可以绑定
int&& r4 = ++i; // 不可以
虽然&&r1绑定了右值,但r1本身是左值(要把它看成一个变量),因为它位于等号左边,同时,因为它是左值,所以左值引用能成功绑定到它
std::move
将之前分配出去的内存不回收,直接转移给新的对象,这种把某一些内存块从原来的主人转成了现在的主人这种动作就叫移动。
C++标准库里的std::move函数就是把一个左值强制转换成一个右值。
对于字符串string来说:
string st = "I love you";
string def = std::move(st); // string里的移动构造函数把st的内容转移到了def中去,这个转移并不是std::move干的
string st = "I love";
std::move(st); // 执行后,st没有变空,其实是压根没变
string st = "I love you";
string&& def = std::move(st); // 这个不会触发string的构造函数,st不会变为空,这行代码只是将st转成右值并绑定到def上
std::move可以理解成:系统希望/建议在调用move时,程序员自己应该如何看待move函数中的形参,希望程序员以一种什么样的态度或者眼光去看move中的形参。
总结左值、右值
左值是一个持久的值,右值是一个短暂的值。
右值短暂是因为其要么是字面值常量,要么是表达式求值过程中创建的临时对象,该临时对象的特性:
- 所引用的对象将要被销毁
- 该对象没有其他用户