C语言左值,运算符的优先级以及结合性探讨
刚刚开始看一本书。《C陷阱与缺陷》,相信学习C语言的大家都对这本书有耳闻。今天看到了里面的贪心法则。也即在读到一个字符后,尽可能多的读入更多的字符,直到读入的字符组成的字符串已经不可能再组成一个有意义的符号为止。下面举例是 a---b。
然后就想起之前每次遇到这种问题都觉得没什么意思,自己写代码基本从来不这样干,写复杂的语句就加小括号。但是偏偏一些人还就问这些问题。今天就专门来钻研探讨一下这一系列相关的问题。
首先,a++和++a都是对变量a的值作自增运算然后将新的值赋给a。
但是当出现在表达式中时,这两种表述就有一点区别。
a++是将a的值先用在表达式里进行运算,运算完成之后再对a进行自加操作。
++a是先对a进行自加操作,然后用新的a的值在表达式中进行计算。
简单的看一下验证代码和结果如下
1 #include<stdio.h> 2 int main() 3 { 4 int a , b; 5 int c; 6 7 a = b = 0; 8 9 c = a++; 10 printf("c = %d\n" , c); 11 12 c = ++b; 13 printf("c = %d\n" , c); 14 return 0; 15 }
>>>c = 0
>>>c = 1
那么结合贪心法则。a+++b就应该是(a++)+b。事实证明确实如此。
1 #include<stdio.h> 2 int main() 3 { 4 int a , b; 5 int c; 6 7 a = b = 0; 8 c = a+++b; 9 printf("c = %d\n" , c); 10 11 a = b = 0; 12 c = (a++)+b; 13 printf("c = %d\n" , c); 14 15 a = b = 0; 16 c = a+(++b); 17 printf("c = %d\n" , c); 18 19 return 0; 20 }
>>>c = 0
>>>c = 0
>>>c = 1
那么 a+++++b呢?应该是 ((a++)++)+b。
尝试一下。 代码改成 c = ((a++)++)+b; 报错了。
错误信息是 error: lvalue required as increment operand。也就是说自增运算符需要左值。
那么。什么是左值?左(left)的原意是指可以放在赋值符号“=”的左边,但其实也表示能作为&和++等操作符的操作数。
相对应的还有右值。简单的说,就是放在赋值符号“=”的右边的那个东西。
深入一点说。首先,右值的英文叫R-value,其中的R指的是Read,表示可读。一切常数、字符和字符串都是右值。
左值的英文叫L-value,其中的L指的是Location,表示可寻址。
我们知道,在写程序定义一个变量时,计算机会在内存申请一块区域用于存放这个变量的值。
比如说,你写一条语句 int a = 1;
那么首先,这是一个声明。声明了a是一个int型变量。然后是定义,定义的时候内存会申请空间。
类比现实世界。你需要一个箱子来装东西。那先要根据你要装的东西的大小形状等特点选择一个最合适的箱子。这个就是声明。也就是前面的 int 起的作用。int 保证了a这个变量里面存的值应该有哪些特点。
现实世界,你不会用纸箱去装水,因为会漏。也不会用小盒子去装大电视,因为装不下。写程序也是这样,你已经声明了变量的类型之后,你就应该用这个变量装合适的值。
实际上,真正的声明相当于只给了你一个箱子的模型,但是这个时候这个箱子还没有。它只是告诉了你这个箱子应该有哪些特点(形状,大小等等)。
在定义的时候,它就会根据前面的这个模型真正给你一个真实存在的箱子。而你的语句中的a,就相当于给了这个箱子一个名字。从此,你可以通过这个名字找到这个箱子。就好像能喊你的名字找到你一样。
语句最后这个1,说明你把“1”这个值,装到了这个箱子里。
那么现在。a这个箱子就是左值,它可以用来装东西。a箱子里面的东西(变量a的值)就是一个右值,它可以出现在赋值符号(=)右边。
了解了左值和右值之后,我们再来看自增运算符。
a++。它会先返回a的值进行计算(右值),然后对a(左值)进行自增。
++a。它先自增然后返回变量a(左值)。
注意,自增运算符只能作用于左值。也即变量。不能作用于右值。例如 1++ 这样的是不被允许的。
再看这个问题a+++++b。也就是 ((a++)++)+b。
a++返回了一个右值,右值不能再进行自增操作。所以报错。
那么++a+++b呢?根据贪心法则。应该是((++a)++)+b。也就是先算++a
因为++a返回的是一个左值,可以进行自增运算。所以这个表达式貌似正确。
然而。好吧。又报错了。报错信息依然是。error: lvalue required as increment operand。也就是说自增运算符需要左值。
WTF? ++a是返回的左值啊? 没毛病啊?
通过用代码验证证明这个表达式的计算顺序是这样的。(++(a++))+b
注意:根据小时候的数学知识,表达式应该从最里面的括号开始运算,这里也就是第一步先算 a++
然后我考虑是不是优先级的问题。找了一波优先级表。发现自增运算,不论a++还是++a优先级都是2。
想了半天加瞎找。发现了一个东西。叫做结合性。同一优先级的运算符,运算方向由结合性决定。
结合性只用于表达式中出现两个以上相同优先级的操作符的情况,用于消除歧义。事实上你会注意到所有优先级相同的操作符,它们的结合性也相同。
我看了一下,优先级为2,13,14,的运算符结合方向是从右到左。也就是先用右边的运算符计算。其余优先级(优先级总共15)的运算符均为从左到右,也就是我们正常数学上的运算方向。
这样,就解释了上面 ++a+++b 问题。因为它会先执行a++,返回一个右值,然后执行自增操作。右值的自增操作报错。
(运算符优先级和结合性的表格网上能找到,这里不给出)
最后,需要声明一个问题。小括号不会改变自增操作中变量自增和变量值应用的顺序。
如 b = (a++);
它依然会先把a的值赋给b然后执行自加,不会因为你加了小括号就先自加再赋值。