第二章 语法“陷阱”
2.1 理解函数声明
2.2 运算符的优先级问题比如: (*(void(*)( )) 0) ( );
任何复杂表达式其实只有一条简单的规则:按照使用的方式来声明。
任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符(declarator)。声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定类型的结果。最简单的声明符就是单个变量,如:
float f , g;
这个声明的含义是:当对其求值时,表达式 f 和 g 的类型为浮点数类型(float)。因为声明符与表达式的相似,所以我们也可以在声明符中任意使用括号:
float ((f));
这个声明的含义是:当对其求值时,((f))的类型为浮点类型,由此可以推知,f也是浮点类型。
各种形式的声明还可以组合起来,就像在表达式中进行组合一样。因此, float *g( ),(*h)( )表示*g( )与(*h)( )是浮点表达式。因为()结合优先级高于 * ,*g( )也就是*(g( )) : g是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。
一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如,因为下面的声明:
float (*h)();
表示h是一个指向返回值为浮点类型的函数的指针,因此,(float (*)())表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。
(*fp)(); -> (*0)(); -> (*(void (*)())0)();
2.3 注意作为语句结束标志的分号
( () [] -> .)优先级最高者其实并不是真正意义上的运算符,包括:数组下标,函数调用操作符、各结构成员选择操作符。他们都是自左于右结合,因此 a.b.c的含义是(a.b).c。
(! ~ ++ -- - (type) * & sizeof)单目运算符的优先级仅次于前述运算符。在所有的真正意义上的运算符中,它们的优先级最高。单目运算符是自右至左结合。因此*p++会被编译器解释成*(p++)。
优先级比单目运算符要低的,接下来就是双目运算符。在双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,条件运算符(三目运算符),最后是赋值运算符。
算术运算符* / %
+ -
移位运算符<< >>
关系运算符< <= > >=
判等运算符== !=
逻辑运算符& ^ | && ||
条件运算符?:
赋值运算符= += -= *= 等等
我们需要记住的最重要的两点是:(移关咯)
1.任何一个逻辑运算符的优先级低于任何一个关系运算符。(记忆:关咯)
2.移位去处符的优先级比算术运算符要低,但是比关系运算符要高。(记忆:移关)
优先级最低的是逗号运算符。
2.4 switch 语句重要的情形就是 if 或者 while 语句之后需要紧跟一条语句时,注意分号!!!
注意case 后面的break语句。
下面程序作用:编译器在查找符号、时跳过程序中的空白字符。
2.5 函数调用case ' ':
linecount++;
case ' ':
case '':
.......
2.6 “悬挂” else 引发的问题
C语言要求:在函数调用时即使函数不带参数,也应该包括参数列表。
如: f(); 是一个函数调用语句,而 f; 计算函数f的地址,却并不调用该函数。
注意 if 与 else 的配对,当后面有多条语句时用 {} 进行“封装”
if (x == 0)
if (y == 0) error();
else{
z = x + y;
f(&z);
}