C语言中什么叫做左值?右值?

时间:2021-06-21 00:14:14

左值就是在赋值中可以放在赋值操作符两边的值,比如:

int a = 1;
double b = 2.0

a = b;
b = a;

这里a和b都是左值,一切变量都是左值,但const变量是例外。 

*p是一个左值,和变量一样,只要在*p的右边加上赋值运算符,就可改变*p的值。

如果p是一个指向常量的指针,*p就是一个不能修改的左值,即它不能被放到赋值运算符的左边。

i和 -i 都是表达式
但一个是左值(i),一个是右值(-i)。
++,--这两种操作符要求作用于左值,所以i++合法,(-i)++不合法。

不严谨的讲,左值右值的区分在于位于等号的那一侧,左侧的是左值,通常是一个变量,右侧的是右值,可以是一个变量,或者是一个表达式。

先看什么是表达式:
表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。
再看什么是左值:
C++ 中存在两种表达式:左值可以出现在赋值语句的左边或右边。右值只能出现在赋值的右边,不能出现在赋值语句的左边。
另外说明一下,i不仅是一个表达式,它还是一个变量,但是-i却不是一个变量,这是他们一个可以自增一个不能自增的根本原因
对于i++来说,i是一个变量,所以是一个左值,执行i=i+1
但是对于(-i)++来说,-i是一个表达式,而不是一个变量,一个表达式是不可以作为左值的,因为没有办法执行这条语句:-i=-i+1(-i+1的值不能附给-i,因为没有-i这个变量存储空间)

一个赋值表达式:
X = Y;
在这个表达式里,符号X的含义是X所代表的地址,这被称为左值,左值在编译时可知,左值表示存储结果的地方;
在这个表达式里,符号Y的含义是Y所代表的地址的内容,这被称为右值,右值在运行时才可知,如无特别说明,右值表示“Y的内容”。 在《C专家编程(中文版)》中第4章,对左值和右值的基本描述。
而右值则是只可以放在赋值操作符右边的值,比如:
int a = 0;
char *b = "hello";

3 = a; // ERROR
"howdy" = b // ERROR

这里3和"howdy"都是右值,所以不能放在赋值操作符左边,一切常数、字符和字符串都是右值。

l-value 与 r-value 区别
左值是引用某个对象的表达式,就是可以放在赋值左边的东西,如:*(p+1)=7, 没有名字的变量(*(p+1)表达式一定是一个类型的对象)被赋值了,但左值并不一定能被赋值,因为左值可以引用某个常量。 所有的引用都是左值。
右值是表达式的值(不是引用),可以放在赋值右面。
所有的左值都可以是右值,反之不成立
int i, j, *p;
i = 7; // Correct. A variable name, i, is an l-value.
7 = i; // ERROR. A constant, 7, is an r-value.
j * 4 = 7; // ERROR. The expression j * 4 yields an r-value.
*p = i; // A dereferenced pointer is an l-value.
const int ci = 7; // Declare a const variable.
ci = 9; // ERROR. ci is a nonmodifiable l-value


普通引用和const引用的初始化
当引用的初始式是一个左值(是一个对象,你可以取得他的地址)时,其初始化就是非常简单的事情。普通T&的初始式必须是一个T类型的。而cosnt T&则不必是一个左值,甚至可以不是T类型的。在这样的情况下,经过以下几个步骤。
(1)首先,如果需要的话,将应用到类型T的隐式类型转换。
(2)而后,将结果存入一个类型T的临时变量。
(3)最后,将此临时变量用作初始化的值。
例如
double& d=1; //错误,d是左值,不能用右值1 来初始化
const double& cd=1; //ok

对后一个初始化的解释是:
double temp=double(1); //首先建立一个具有正确数据类型的临时变量
const double& cd=temp; //而后用这个临时变量作为cd的初始式,temp是个右值

由于左值的一大特点是可以对其赋值,而const正好将这个特点给阉割了,使该表达式成为了一个右值,所以可以用右值来初始化。

===7.3.1引用参数===
函数的引用参数
把参数声明成引用,实际上改变了缺省的按值传递参数的传递机制。在按值传递时,函数操纵的是实参的本地拷贝。当参数是引用时,函数接收的是实参的左值而不是值的拷贝。
这意味着函数知道实参在内存中的位置,因而能够改变它的值或取它的地址。
int obj;
void frd( double & );
int main()
{
frd( obj ); // 错误: 参数必须是 const double &
return 0;
}
对frd()的调用是错误的.实参类型是int 必须被转换成double 以匹配引用参数的类型,
该转换的结果是个临时值,因为这种引用不是const 型的,所以临时值(不是左值)不能被用来初始化该引用


什么时候将一个参数指定为引用比较合:
1、允许改变实参的值
2、向主调函数返回额外的结果
3、向函数传递大型类对象

使用指针还是引用作为参数的一个重要区分:
引用必须被初始化为指向一个对象,一旦初始化了,它就不能再指向其他对象,而指针可以指向一系列不同的对象也可以什么都不指向
所以,如果一个参数可能在函数中指向不同的对象,或者这个参数可能不指向任何对象,则必须使用指针参数

===7.3.3 数组参数===
在C++中数组永远不会按值传递它是传递第一个元素(准确地说是第0 个)的指针
如下声明
void putValues( int[ 10 ] ); //当编译器对实参类型进行参数类型检查时,并不检查数组的长度,编泽器忽略10
被编译器视为
void putValues( int* );
数组的长度与参数声明无关,因此下列三个声明是等价的
// 三个等价的putValues()声明
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );

传递数组长度的三种机制
1、提供一个含有数组长度的额外参数:void putValues( int[], int size );
2、将参数声明为数组的引用:void putValues( int (&arr)[10] );
当参数是一个数组类型的引用时,数组长度成为参数和实参类型的一部分,编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配
// 参数为10 个int 的数组
// parameter is a reference to an array of 10 ints
void putValues( int (&arr)[10] );//只接受10 个int的数组
int main() {
int i, j[ 2 ];
putValues( i ); // 错误: 实参不是 10 个 int 的数组
putValues( j ); // 错误: 实参不是 10 个 int 的数组
return 0;
}

3、使用抽象容器
void putValues(const vector<int> &vec );