第二章 语法陷阱
2.1 理解函数声明
任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符(declarator)。
float *g(), (*h)();
表示*g()与(*h)()是浮点表达式。因为()结合优先级高于*,*g()也就是*(g()):g是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。
给定类型的类型转换符,只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如,下面的声明:
float (*h)();
表示h是一个指向返回值为浮点类型的函数的指针,因此,
( float (*) () )
表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。
( *(void(*)())0)() : 调用首地址为0位置的子例程
void(*)() : 一个指向返回值为void类型的函数的指针
(void(*)())0 : 将常数0转型为“指向返回值为void的函数的指针”类型
2.2 运算符的优先级问题
FLAG是一个整数,且该整数值的二进制表示中只有某一位是1,其余各位均为0。
flags也是一个整型变量,需要判断它在常量FLAG为1的那位上是否同样为1。
if ( flags & FLAG )
为了使可读性更好,显示判断表达式的值是否为0,写法如下:
if ( flags & FLAG != 0 )
虽然更好懂了,却是一个错误的语句。因为!=的优先级高于&,上式等价于
if ( flags & ( FLAG != 0 ) )
hi 和 low是两个整数,值介于0到15之间,如果r是一个8位整数,且r的低4位与low各位商的数一致,而r的高4位与hi各位上的数一致。写成如下:
r = hi << 4 + low ;
但是很不幸,这样写是错误的。加分运算的优先级比移位运算的优先级高,因此,本例实际上相当于:
r = hi << ( 4 + low );
更正方法1:加括号 r = ( hi <<4 ) + low;
更正方法2:将加号改为按位逻辑或 r = hi << 4 | low; //这种方法牵涉到移位与逻辑运算的优先级,就更加不明显了。
优先级最高者其实并不是真正意义上的运算符,包括:数组下表、函数调用操作符各结构成员选择操作符。它们都是自左于右,因此a.b.c的含义是( a.b ).c,而不是a.( b.c )
单目运算符仅次于上面所述运算符。在所有的真正意义上的运算符中,它们的优先级最高。因为函数调用的优先级高于单目运算符的优先级,所以,如果p是一个函数指针,要调用p所指向的函数,必须这样写:( *p )()。 如果写成 *p() 编译器会理解成 *( p() )。类型转换也是单目运算符,它的优先级和其他单目运算符的优先级一样。单目运算符是自右至左,因此 *p++ 会被编译器解释成 *(p++),即取指向p所指向的对象,然后将p递增1;而不是(*p)++,即取指针p所指向的对象,然后将该对象递增1。
优先级比单目运算符要低的,接下来是双目运算符。在双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,条件运算符,最后是赋值运算符。
==和!= 的优先级要低于其他关系运算符。因此如果要比较a与b的相对大小顺序是否和c与d的相对大小顺序一样,可以这样写: a < b == c < d
^按位异或运算符
while ( c = getc ( in ) != EOF )
putc ( c, out );
等价于 while ( c = ( getc ( in ) != EOF ) )
2.3 注意作为语句结束标志的分号
2.4 switch语句
2.5 函数调用
如果f 是一个函数,f(); 是一个函数调用语句,而 f; 却是一个什么也不做的语句。更精确地说,这个语句计算函数f的地址,却不调用该函数。
2.6 悬挂else引发的问题
Else始终与同一对括号内最近的未匹配的if结合。