目录

左值/右值/左值引用/右值引用

前言

  1. 此篇博客会按照一个逻辑推导 带你们区分到底什么是 左值/右值/左值引用/右值引用

逻辑推导1

我们来看这一行简单的代码

1
int i = 10;

i 在左边 -> 左值 / 10 在右边 -> 右值 这句话,在这行代码是适用的,但是!这句话不准确

我们看 i 在内存中是有位置的实际变量 / 而 10 没有存储地址,也就是在内存中是没有职位的

所以我们不能说 10 = i;,因为10是没有位置的,不能存储数据

但是 i 是一个左值,我们可以用另一个变量a 等于 i

1
int a = i;

这就是为什么说 “i 在左边->左值 / 10 在右边->右值” 这句话是不准确的

逻辑推导2

现在我们来加一个函数

1
2
3
4
int GetValue()
{
  return 10;
}

随后调用这个函数

1
int i = GetValue();

在这个例子中,GetValue返回了一个临时值,也就是右值,然后将这个右值保存到左值

当然因为这是一个右值,所以我们是这给这个右值赋值,那么就会失败

1
GetValue() = 10; // Error!!!

我们在试着修改一下,如果将函数返回值改成返回左值,那就变得很有趣了

1
int& GetValue() { }

将"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之外 你不能从这个左值中窃取任何东西,因为他可能会在很多函数中使用

总结

  1. 左值是有某种存储支持的变量
  2. 右值是临时值
  3. 左值引用仅仅接受左值 除了const
  4. 右值引用仅仅接受右值