《C和指针》这本书上讲到左值和右值,概念有点含糊
据我自己的理解:
左值:标识的应该是个存储位置,内存中的位置,左值可以是个变量名,或者是个表达式,但表达式必须表示的是个内存位置
右值:就是个值,变量的值,表达式的值
操作符的属性有3个因素:操作符的优先级,操作符的结合性,操作符是否控制执行顺序。
操作符的优先级:决定含有多个操作符的表达式的求值顺序,每个操作的优先级不同
操作符的结合性:决定相同优先级的操作符是从左到右计算,还是从右到左计算。
操作符是否控制执行顺序:对表达式的求值顺序施加控制,有4个:&&(逻辑与) ||(逻辑或) ?:(条件操作) ,(逗号运算符)
注1:lexp表示左值表达式,rexp表示右值表达式,L-R表示从左到右求值,R-L表示从右到左求值
注2:左值同样可以作为右值,但右值却不能作为左值用
1. () 聚组
聚组就是加括号对表达式的计算优先级施加影响,这个是经常用到的,优先级最高,最先计算聚组内的值,如(5+3),结果与表达式的值相同,也就是同5+3,聚组肯定可以表示一个右值,因为 a=(5+3) 这个表达式的正确的,聚组可以表示右值,但聚组能否表示一个左值,如(a)=5+3; (b[5]) = 5+3; 这两个表达式能合理通过编译,证明了聚组可以表示为左值,所以正如图上所述:聚组的结果类型,与表达式的结果类型相同,表达式若表示的是个左值则聚组就表示的是个左值,表达式若表示是个右值则聚组就表示是个右值。
2. () 函数调用
函数调用的用法示例:rexp(rexp,...,rexp),说明函数名是个右值,而且函数名单独作为右值使用表示的是执行这个函数的地址,这个地址在编译完成就确定其地址,不能对函数名表示的地址值就行修改,如:int func() { ... } 这个函数,func就表示一个右值,不能表示为左值,也就是不能用 func=...,估计没人会这样用,如果定义一个指针 int (*f)() = func; 然后通过 (*f)() 来调用函数,这个(*f)表示为一个聚组,返回的值是*f的类型,是个左值,同样可以作为右值用,同函数调用rexp(rexp,...,rexp)不冲突,函数调用不限制rexp必须是函数名,可以是任何表达式,如:将函数指针定义到结构体中,然后通过结构体变量来访问函数指针。需要通过聚组来提高表达式的优先级。
函数调用的参数:全是右值,正是说明了函数调用都是传值的,当然传递的左值到函数参数时都会全部转变成右值。如:int a=10; func(a); 这里a就变成了右值
函数调用的结果类型:也是右值,就说明了函数只能返回值,不能这样用:func() = a;
函数调用的结合性:L-R,因为找不到和函数调用相同优先级的表达式,这里就不过多说明。
3. [] 下标引用
下标引用的用法示例:rexp[rexp],说明数组名是个右值,如:int a[10]; a[5] = 5; 不能使用a = 5;这样的表达式,数组名也是编译后确定内存地址的,数组名只能做右值,看看这个:int a[10]; int *p = &a; 这么用是合法的,与int *p=a表示的是一个意思,但是会警告,对数组名取地址,&a表达式返回的类型就变成了指向数组这一块内存的指针,而不是指向数组中单个元素的指针,所以与p的类型不同,就会警告,然而取地址符的用法:&lexp要求是个左值表达式,似乎好像数组名a又可以解释为左值,所以数组名到底是左值还是右值,要依据上下文环境而定,若是下标引用a[] 则数组名是个右值,若是取地址&a 则数组名是个左值,但是数组的索引就必定是个右值了。
下标引用的结果类型:左值,下标引用所引用的是个具体的地址,索引是个左值,如 a[5] = 5;
下标引用的左结合性也是理所当然:如,int a[10][10]; a[5][5] = 5; 这里有两个相同优先级的下标引用,所以必定是先计算出a[5],作为右值,然后再计算出a[5][5],作为左值,然后才赋值。
4. . 访问结构成员
lexp.member_name,看来定义的结构体变量是作为左值,因为结构体变量是有存储地址的,而其访问成员的结果类型也是作为左值,因为成员变量也是有存储地址的,结合性从左到右,如 a.b.c 这样的成员访问,是先访问结构体a的成员b再访问b的成员c,所以必定为左结合性。
5. -> 访问结构体指针成员
rexp->member_name,也就是说执行结构体的指针变量其本身是个左值,有存储地址,但是访问成员时就作为右值来用了,其余的同访问结构成员
6. ++ -- 后缀自增和自减
lexp++ lexp-- 是个左值,后缀自增和自减的原意就是加完和减完后还要存回原来的地方,暗含了个存储地址,既然是左值表达式,则 [](下标引用) .(访问结构成员) ->(访问结构体指针成员) *(间接访问) 都可以作为自增自减的表达式。
返回的结果类型是个右值,就不能再进行自加或自减了,如:(lexp++)++ 这么做完全是错误的。
结合性:由于自加自减不可能会多次调用,没太大意义
7. ! ~ + - 逻辑反 按位反 正值 负值
这四个要求的全部都是右值表达式,返回的值也都是右值,还是右结合性的,也就是说如下用法都可以: !rexp(rexp,...,rexp); +-lexp++; -*rexp; +sizeof rexp; ~func();
右结合性即:+-lexp++ 等同于 +(-(lexp++))
8. ++ -- 前缀自增和自减
同后缀的自增和自减的形式
9. * 间接访问
* rexp ; rexp是一个指针变量或者一个指针表达式,如:*p; *p++; *func(); * a[5]; *rexp->member; *rexp->member++; *++rexp.member;
右结合性要求:*****p 等同于 *(*(*(*(*p))));
10. & 取地址
&lexp; 只能对左值表达式取地址,如:&a; &*p; &a[5]; &a[5]++; &rexp.member; &rexp->member; 注:红色标注的是个错误的语法
右结合性:找不出多次取地址的表达式。
11. sizeof (类型)
右结合性:sizeof sizeof sizeof rexp 就等同于 sizeof ( sizeof (sizeof rexp)))
(类型)(类型)(类型)rexp 等同于 (类型) ( (类型) ( (类型)rexp ) )
12. 其余的运算符由于比较简单不再讨论。
总结:能产生左值的表达式就4个:[](下标引用) .(访问结构成员) ->(访问结构体指针成员) *(间接访问)
需要使用左值表达式的有:访问结构成员,自加自减, 取地址。