2.1 理解函数声明
函数的声明与变量类似,类型 + 声明符,声明符与表达式相似,所以在声明符中可以可以使用括号。
float ((f));
该声明代表对其求值时,((f))为浮点型,则 f 也是浮点型。函数和指针的声明中也是一样的。
float ff();
表达式 ff() 的结果是一个浮点数,即它是一个返回值为浮点类型的函数。
float *pf;
说明 pf 是一个指向浮点数的指针。
float *g() , (*h)();
表示 *g() 与 (*h)() 都是浮点表达式。()
的优先级高于 *
,*g()
= *(g())
,g是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出 h 是一个函数指针,h 所指向的函数的返回值是浮点型。前者是返回浮点型指针,后者是指向返回浮点数的函数。
(float (*)())
将 h 和分号去掉,再用括号括起,该表达式就变成了表示一个 ==“指向返回值为浮点类型的函数的指针”==的类型转换符。
分两步分析(*(void(*)())0)()
第一步
(*fp)();
此为调用一个函数指针 fp 的方法,可简写为 fp()
,*fp 两侧的括号非常重要,因为括号优先级高于星号,所以如果没有两侧的括号,就变成了 *fp()
实际上与*(fp())
含义相同,它是*((*fp)())
的简写。
第二步
找个表达式替换 fp 。
对 (*0)();
中的 0 做类型转换,转换类型为 “指向返回值为 void 类型的函数的指针” 。
如果说 fp 是这样类型的一个指针,(*fp)()
的值就为 void ,默认 fp 初始化为0,fp 的声明如下:
void (*fp)();
(*fp)();
这种写法的代价是多声明了一个哑变量,知道如何声明一个变量,便可以对一个常数进行该变量类型的转换,去掉变量声明中的变量名即可。
所以对 0 做的类型转换即为
(void (*)())0
因此,可以用该表达式替换 fp ,从而得到:
(*(void(*)())0)();
分号使该表达式被调用。
使用 typedef 可以简化过程:
typedef void (*funcptr)();
(*(funcptr)())();
2.2 运算符的优先级问题
- 判断两个整型变量是否有同一位为 1,一般写作
if (flag & FLAG)
,但如果为了更好理解写成if (flag & FLAG != 0)
虽然更好理解,但却是错误表达,因为 != 的优先级要高于 & 运算符,所以被编译器理解为了if (flag & (FLAG != 0) )
。 +
加法运算优先级高于<<
移位运算,例如一个八位整数的高四位整数加低四位整数运算,应该写做r = (hi<<4)+ low;
或者r = hi<<4 | low;
,因为|
的优先级低于<<
,如果直接写r = hi << 4 + low;
会被理解为r = hi << (4 + low)
。- 优先级最高的包括:数组下标、函数调用操作符、各结构成员选择操作符。都是自左向右结合,如
a.b.c
的意思是(a.b).c
而不是a.(b.c)
。 - 单目运算符的优先级仅次于操作符等,所以函数调用的优先级要高于单目运算。因此,调用 p 指向的函数,必须写作
(*p)()
如果写成*p()
会被解释为*(p())
。 - 类型转换的优先级与单目运算相同,单目运算符是自右向左结合,所以
*p++
会被解释为*(p++)
,本意是将 p 指向的对象进行加一,结果变成先取 p 所指向的对象,然后 p 加一,因为 ++ 放到后边本意就是先运算再加一。 - 双目运算符优先级低于单目,双目中算数运算符优先级最高,其次是移位、关系、逻辑、赋值、条件(条件运算符实际为三目运算符)。
- 任何一个逻辑运算符的优先级低于任何一个关系运算符。
- 移位运算符的优先级比算术运算符要低,比关系运算符要高。
==
和!=
的优先级低于其他关系运算符。比较 a 与 b 的大小顺序是否与 c 和 d 的大小顺序相同,就可以这样写a < b == c < d
- 任何两个逻辑运算符都具有不同的优先级。按位与高于顺序运算,与运算高于或运算,按位异或介于按位与和按位或之间。
- 三目条件运算符是运算符中优先级最低的。所以可以在其表达式中包含关系和逻辑运算符的组合。例如:
rate = income>400 && residency<5 ? 3.5 : 2.0;
该例子也说明了,赋值运算的优先级低于条件运算符是有意义的。 - 逗号是所有运算符中优先级最低的。
- 涉及到赋值运算时容易引起优先级混淆,尽量使用括号括起。例如:
while (c=getc(in) != EOF)
putc(c,out);
本意是先将 getc(in) 获得的值赋给 c ,然后 c 再与 EOF 进行比较。但因为赋值的优先级低于比较,所以实际的含义变成了将 getc(in) 获得的值与 EOF 进行是否相同比较,将比较结果赋给 c。所以最后 c 的值就是一串二进制字节流。应改写为如下样例:
while ((c=getc(in)) != EOF)
putc(c,out);
2.3 注意作为语句结束标志的分号
- 注意if判断的条件表达式后,不要误加分号。
- 注意循环条件后。不加误加分号。
- 函数声明后记得加分号。
2.4 switch 语句
注意每个case
语句后的break
,不要忘记,否则会一直执行直到遇到下一个 break
。特殊操作可以省略 break
提高程序效率。
2.5 函数调用
调用函数必须加参数列表(即函数名后必须加括号)。
2.6 “悬挂”else引发的问题
else
总是与同一对括号内最近的未匹配的 if
结合。所以 if
之后的语句最好使用大括号进行分隔。