C语言之过
挑了一些 C 语言中的容易被人忽略的细微之处,C语言的新手常常在这些问题上想当然,其结果自然是 BUG。
错误的优先级
优先级问题 | 表达式 | 人们可能误以为的结果 | 实际结果 |
---|---|---|---|
.的优先级高于*。 | *p.f | p 所指对象的字段 f | 对 p 取 f 偏移,作为指针,然后进行解除引用操作。 |
[]高于* | int *ap[] | ap 是个指向 int 数组的指针 | ap 是个元素为 int 指针的数组 |
函数()高于* | int *fp() | fp 是个函数指针,所指函数返回 int。 | fp 是个函数,返回 int * |
==和!=高于位操作符 | (val & mask != 0) | (val & mask) != 0 | val & (mask != 0) |
==和!=高于赋值符 | c = getchar() != EOF | (c = getchar()) != EOF | c = (getchar() != EOF) |
算术运算高于移位运算符 | msb << 4 + lsb | (msb << 4) + lsb | msb << (4 + lsb) |
逗号运算符在所有运算符中优先级最低 | i = 1, 2 | i = (1, 2) | (i = 1), 2 |
这些运算符的优先级问题都值得我们好好想想,并牢记在脑海中。对 C 语言的声明有疑问的,比如说上面表格中的二、三行,可以参考另一篇笔记《C语言复杂声明分析》。
我们在此再讨论一下上面表格中的最后一行,涉及逗号的情况有时会让程序员歇斯底里。例如,
i = 1, 2;
我们知道逗号运算符的值就是最右边操作数的值。但在这里,赋值符的优先级更高,所以实际情况应该是:
(i = 1), 2; /* i的值为1 */
i 赋值为 1 接着执行常量 2 的运算,计算结果被丢弃。最终,i的结果是 1 而不是 2。
有些专家建议在 C 语言中牢记两个优先级就够了:乘法和除法先与加法和减法,在涉及其他的操作符时一律加上括号。
结合性
操作符的优先级已经够让人心烦的了,许多人对操作符的结合性同样感到困惑。在标准 C 语言的文档里,对操作符的结合性并没有作出非常清楚的解释。
总结起来,倒比较简单。所有的赋值符(包括复合赋值符)都具有右结合性,就是说表达式中最右边的操作最先执行,然后从右到左依次执行。类似地,具有左结合性的操作符(如位操作符=&=和=|=)则是从左至右依次执行。如果在计算表达式中的值时需要考虑结合性,那么最好把这个表达式一分为二或者使用括号。
最大一口策略
ANSI C 规定了一种逐渐为人所熟知的“maximal munch strategy(最大一口策略)”。这种策略表示如果下一个标记有超过一种的解释方案,编译器将选取能组成最长字符序列的方案。如:
z = y+++x;
程序员的可能是 z = y + ++x
,但也可能是 z = y++ + x
。根据最大一口策略,上面这个例子会被解析为 z = y++ + x
。再如:
z = y+++++x;
按照最大一口策略会被解析为 z = y++ ++ +x
,这将引起一个编译错误,而我们实际上想要的结果为 z = y++ + ++y
。当然需要这种情况最好的办法是用空格或和括号将标记隔开,这样一开就不会有任何异议也不会出现编译错误了。