前言

在C语言中什么叫左值和右值

1
2
int a = 3;
//a为左值,3为右值
  • 左值一般是变量,在程序运行时有一个准确的地址和值,除了const的情况下一般都能修改。
  • 右值则一般是常量或者临时对象,除非强转之类的操作,其它情况下一般不能修改

在编译报错的时候应该或多或少也见过到提示右值不可被修改

不过上述的说法也并不绝对

1
2
3
int a = 1;
int b = 2;
a = b;

这里的右值b它是一个变量。


正文

1
2
3
4
5
6
7
8
9
10
void AddSum(int a){
std::cout << a << "\n";
}

int main(){
int a = 10;
AddSum(10 + 20);

return 0;
}

在正常情况下,int a = 10; 编译器把10转换成十六进制赋值给ebp-8的位置上
而调用函数传参的时候使用算术表达式他也会默认先算好,可能这里函数的反汇编不明显
稍微改动一下让a+10+20

这里就很清楚的看到编译器先将ebp-8的地址传给eax寄存器,然后eax寄存器在加上1E,也就是30

正常的变量作为左值都好解释,数组和指针的时候:

1
2
3
4
5
6
7
8
9
10
11
int array[10];
int *p = array; //array是数组,默认表示数组首地址array[0]。所以不需要加&。

array[0];//属于一个合格的左值,有具体位置
*p 和 p //也属于左值,*p在此处表示array[0]的地址,而p本身是个指针,指针也有他自己的地址

&array[0]; //就不属于了,这个标识array[0]的地址,做不了左值

*(p+1); //这个就有点意思了,其实就是array[1],或者说p[1]
p+1; //就不对头了,这只是让地址单纯偏移一个类型长度,得到的是地址,


左值引用和右值引用

1
2
3
4
int a = 10;
int &b = a; //b引用了a
int &&c = b; //但此处引用c不能引用b,这种多级套娃的形式在指针里是可以的
//引用的原则就是int &c = b;这样是合法的。

编译器提示无法将 右值引用绑定到左值,

那么两个&该怎么用:

1
int &&c = 150+120;

显然是给他引用一个右值为这种算术表达式。但是注意修改c没有太大意义,因为右值是固定的。
那么右值引用的场景在哪。

1
2
3
4
5
6
7
8
9
10
11
12
13
void AddSum(int &a){
std::cout << a << "\n";
}

int main(){
int a = 10;
int &b = a;
int &&c = 150+120;

AddSum(a + 150 + 200);

return 0;
}

当函数参数为左值引用时,没办法传递表达式进去,所以右值引用的场景就来了

1
2
3
4
5
6
7
8
9
10
11
12
13
void AddSum(int &&a){
std::cout << a << "\n";
}

int main(){
int a = 10;
int &b = a;
int &&c = 150+120;

AddSum(a + 150 + 200);

return 0;
}

当然可以先用一个变量接受这个表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void AddSum(int &a){
std::cout << a << "\n";
}

int main(){
int a = 10;
int &b = a;
int &&c = 150+120;

int x = a + 150 + 200;
AddSum(x);

return 0;
}

这个是没啥问题,语义是通顺的。

int x毕竟是额外开辟了内存空间去保存这个表达式的值,虽然临时变量也会占内存,但是至少临时变量销毁比局部变量快。


题外话

&在C语言中,我们通俗的说是取地址符,而在cpp中多了引用的概念
区分的方式就是 参照 =
当& 在变量定义区域,表示引用:int &b = a
当& 在变量操作区域,表示取地址:int *p = &a;

然后来个玄乎的 &*p 和 *&p,在变量定义区域时:

1
2
3
4
5
int a = 10;
int *p = &a;

int *&p1 = p; //合法
int &*p2 = p; //不合法

int *&p1 = p先当与指向一个引用,指针引用。
int &*p2 = p引用一个指针显然是不对的。引用本身就是一个弱化版本的指针,编译器也提示错误

在变量操作时:

1
2
3
int a = 10;
std::cout << *&a << std::endl; //合法,相当于*(&a),其实就是做了一次解引用的操作,指向a的地址,使用起来就是a的值。
std::cout << &*a << std::endl; //error,很简单用法问题,a是int类型的变量,直接取地址一个指针a在此处不合理。

修改成指针呢?

1
2
3
4
5
int a = 10;
int *p = &a;
std::cout << &a << std::endl;
std::cout << *&p << std::endl; //上述说过相当于*(&p),&p的地址上存放的是a的地址,*就变成解读&p
std::cout << &*p << std::endl; //而*p表示a的值就是10,&(*p),就是相当于&a;

可以看到地址都是一样的。

这个纯看理解,不行多试几个例子或者看看反汇编。重在能区分。


结语

自古指针都是很恶心的东西,不管放到哪里。要合理区分&是引用还是取地址的情况。