前言
- 此篇博客会按照一个逻辑推导 带你们区分到底什么是 左值/右值/左值引用/右值引用
逻辑推导1
我们来看这一行简单的代码
i 在左边 -> 左值 / 10 在右边 -> 右值 这句话,在这行代码是适用的,但是!这句话不准确
我们看 i 在内存中是有位置的实际变量 / 而 10 没有存储地址,也就是在内存中是没有职位的
所以我们不能说 10 = i;,因为10是没有位置的,不能存储数据
但是 i 是一个左值,我们可以用另一个变量a 等于 i
这就是为什么说 “i 在左边->左值 / 10 在右边->右值” 这句话是不准确的
逻辑推导2
现在我们来加一个函数
1
2
3
4
|
int GetValue()
{
return 10;
}
|
随后调用这个函数
在这个例子中,GetValue返回了一个临时值,也就是右值,然后将这个右值保存到左值
当然因为这是一个右值,所以我们是这给这个右值赋值,那么就会失败
1
|
GetValue() = 10; // Error!!!
|
我们在试着修改一下,如果将函数返回值改成返回左值,那就变得很有趣了
将"int"加上"&“就变成了左值引用 这时候需要为我的值提供某种存储空间 比如一个静态int,然后返回他
1
2
3
4
5
6
7
8
9
10
11
|
int& GetValue()
{
static int value = 10;
return value;
}
int main() {
int i = GetValue();
GetValue() = 10; // true! 我就可以给他赋值,这个表达式也没有啥问题了,这就是左值引用
return 0;
}
|
逻辑推导3
我们再再再展开看一下,现在再加了一个SetValue()
1
2
3
4
5
6
7
8
|
void SetValue(int value) { }
int main()
{
int i = value;
SetValue(i); // true!
SetValue(10); // true
}
|
当函数调用时,传左值是可以的,传右值也是可以的,因为这个右值会被用来创建一个左值
So? 我们可以马上看出,哪个变量是临时的,哪个不是,这个规则就是你不能将右值赋给左值引用
我们可以很容易的去检查这个,如果我在 int 加一个 &,现在我在取一个 referebce to int,这是一个左值引用,SetValue(10)马上这个就会error
因为非const引用的初始值必须是左值
所以 const int& i = 10; 这个是一个特殊的规则
实际情况就是 编译器可能会用你的存储创建一个临时变量,然后赋值给那个引用
const int& i = 10; ===> int temp = 10; const int& a = temp;
所以实际上 还是不可避免的创建了一个左值 但是也同时支持了左值右值
题目
好! 讲到了这里 来出道题目 来看看你们到底懂得了怎么区分左右值
题目: 请找出 所有的左值 和 所有的右值!
1
2
3
4
5
6
|
int main()
{
std::string firstName = "lin";
std::string secondName = "ting";
std::string fullName = firstName + secondName;
}
|
答案
1
2
3
4
5
6
7
|
/*
左值: firstName/secondName/fullName
右值: "lin"/"ting"/firstName + secondName
"firstName + secondName" 为什么是右值
因为 两个字符串相加是一个临时变量 所以他是临时值 也就是右值
*/
|
逻辑推导4
好 我们再次进行拓展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void PrintName(std::string& name)
{
std::cout << name << std::endl;
}
int main()
{
std::string firstName = "lin";
std::string secondName = "ting";
std::string fullName = firstName + secondName;
PrintName(firstName); // true!!
PrintName(secondName); // true!!
PrintName(firstName + secondName); // error!! 因为他是一个右值
}
|
这就是为什么会看到很多用c++写的常量引用 因为他支持左右值,所以我们可以通过这种方法来检测哪些是左值 哪些是右值
那我们有没有办法写一个函数,只接受临时对象,肯定是有的,那就是右值引用
右值引用跟左值引用差不多,只不过多了一个”&“符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void PrintName(std::string&& name)
{
std::cout << name << std::endl;
}
int main()
{
std::string firstName = "lin";
std::string secondName = "ting";
std::string fullName = firstName + secondName;
PrintName(firstName); // error!! 右值不能绑定到左值
PrintName(firstName + secondName); // true!!
}
|
这就说得通了!这很coooool,因为这意味着我们可以写函数重载,他只接受临时对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
void PrintName(const std::string& name)
{
std::cout << name << std::endl;
}
void PrintName(std::string&& name)
{
std::cout << name << std::endl;
}
int main()
{
std::string firstName = "lin";
std::string secondName = "ting";
std::string fullName = firstName + secondName;
PrintName(firstName); // true!!
PrintName(firstName + secondName); // true!!
}
|
那么他有啥作用呢
作用非常大 尤其是在移动语义方面 当然 我这里不会讲这个
这里代码的主要优势是优化,如果我们传入的是一个临时对象,那么我们就不需要担心他们是否活着 是否完整 是否拷贝,我们可以简单的偷他的资源 给到特定对象 或者其他地方使用它们,因为我们知道他是暂时的 他不会存在很长时间, 而如果你传入的不是右值,除了他是const之外 你不能从这个左值中窃取任何东西,因为他可能会在很多函数中使用
总结
- 左值是有某种存储支持的变量
- 右值是临时值
- 左值引用仅仅接受左值 除了const
- 右值引用仅仅接受右值