85.左值与右值 in C++
🍅左值和右值是什么
现在又很多人称左值为有地址的值,因为它们是有地址的
如果要给一个具体的定义
- 左值是由某种存储支持的变量
- 右值是临时值
int i = 10;
如上表达式有两部分,左边和右边
- 左值绝大多数时候在等号左边,右值在右边(但这并不总是适用的!)
- 可以将右值赋值给左值,但不能给一个右值赋值(比如给10赋值)
int i = 10;
int a = i;
在这里等号左边是左值,右边同样是左值,所以才说等号右边就是右值的说法是错的❌
右值不仅仅是字面量,它还可以是函数的结果👇
#include <iostream>
int GetValue() {
return 10;
}
int main() {
int i = GetValue();
}
在这个例子中,函数返回了一个右值,一个临时值
而如果函数返回的是左值,那就变得有趣了,我们可以通过返回int &
来实现,而这就叫做左值引用
🍅左值和左值引用
#include <iostream>
int &GetValue() {
static int value = 10; //提供了储存空间
return value;
}
int main() {
GetValue = 5; //这样做是可行的!!!
}
再展开说一下,如果我有一个函数,它有一个值,那么可以用很多方法去调用这个函数。我可以用左值或者右值来调用它
#include <iostream>
void SetValue(int value) { //什么都没有
}
int main() {
int i = 10;
SetValue(i); //这里的参数便是一个左值
SetValue(10); //这里的参数便是一个右值(临时变量右值)
}
如上可以马上看出来哪个是临时的,哪个不是,因为有一个规则是你不能将右值赋给左值引用,所以左值引用的只能是左值
-
void SetValue(int &value) {} SetValue(10); //❌❌❌报错,这里的形参必须是一个左值
上面的操作其实有个特殊的规则,它是一种变通的方法
-
int &a = 10; //同上,是错的❌ const int &b = 10; //这个是对的✅ /* 真实情况 int temp = 10; const int &b = temp; */
编译器会创建一个临时变量,然后把它赋值给那个引用。故这个操作其实不可避免的创建了一个左值,使得它能同时支持了左值和右值
故其实**
const
左值引用**实际上可以同时接受左值和右值
再谈字符串的例子
std::string a = "123";
std::string b = "456";
std::string c = a + b; //a + b是一个临时变量!
如上式中,等号左边的皆是左值,右边的皆是右值。这里比较迷的是a + b
是右值。
- a和b组成了一个临时字符串,然后把它赋值给了一个左值
故在下面的例子便可展示这点
void PrintStr(string &str) {
std::cout << str << "\n";
}
std::string a = "123";
std::string b = "456";
PrintStr(a + b); //❌这是错的,因为这是个右值
/*
void PrintStr(const string &str) { //如果这里加一个const,那么就对了
std::cout << str << "\n";
}
*/
所以这便是为什么很多用C++写的常量引用。因为它们兼容临时的右值和实际存在的左值变量!
所以这里就有一个很简单的办法检验一个量是左值还是右值:可以写一个非常量的左值引用,因为左值引用只能接受左值
那有没有办法写一个函数,只接受临时对象呢?当然有,因此这里要用到右值引用
🍅右值和右值引用
右值引用看起来和左值引用差不多,不过右值引用需要两个&&
void PrintStr(string &&str) { //注意这里两个&
std::cout << str << "\n";
}
std::string a = "123";
std::string b = "456";
std::string c = a + b;
PrintStr(c); //❌这是右值引用
PrintStr(a + b); //✅
右值引用中,不能传递左值,但可以传递右值
确实有点猛,但是这玩意有什么用呢?答案是:它非常有用,尤其是在移动语义方面
这里的主要优势在于优化
- 如果我们知道传入的是一个临时对象的话,那么我们就不需要担心它们是否活着,是否完整,是否拷贝。我们可以简单地偷它的资源,给到特定的对象,或者其他地方使用它们。因为我们知道它是暂时的,它不会存在很长时间
- 而如果如上使用
const string& str
,虽然可以兼容右值,但是却不能从这个字符串中窃取任何东西!因为这个str可能会在很多函数中使用,不可乱修改!(所以才加了const) - 而在
PrintStr(a + b)
这里显然是暂时的,这个右值只会在这个特定的PrintStr()
调用中被使用
最后回顾定义
- 左值是由某种存储支持的变量;左值有地址和值,可以出现在赋值运算符左边或者右边。
- 左值引用仅仅接受左值,除非用了
const
兼容
- 左值引用仅仅接受左值,除非用了
- 右值是临时值;右值只有值,只能出现在赋值运算符右边。
- 右值只有值,没有地址, 右值是一个优化技巧(C++),因为右值往往是临时变量的。
- 右值引用仅仅接受右值
所以左值和右值的差别便在于有没有地址这一说!!!