2. 语法陷阱
2.1 运算符优先级
看上去琳琅满目,但有规律可循:
- 优先级最高为括号、后缀、下标这一类
- 单目运算符 的优先级仅次于上述, 高于双目运算符。
- 双目运算符:算数运算符 ->移位运算符 ->关系运算符 ->逻辑运算符 ->赋值运算符 ->条件运算符
- 单目运算符、赋值运算符和三目运算符的结合顺序是
自右向左
,其余都是自左向右
举例:
*p++
会是*(p++)
:自右向左结合while(c=get(in) != EOF)
有错,赋值运算符优先级低,结合有错,应为while((c=get(in)) != EOF)
2.6 悬挂else引发的问题
if(x == 0)
if( y== 0)
error();
else
{
z = x + y;
f(&z);
}
其实际含义为:
if(x == 0)
{
if( y== 0)
error();
else
{
z = x + y;
f(&z);
}
}
3. 语义陷阱
- 对于数组a,除了被用作sizeof的参数这一情形,在其他所有场景中数组名a都代表指向数组a中下标为0的元素的指针。
- sizeof(a)得到的是整个数组的大小而不是指向数组a的元素的指针的大小。
对于二维数组 int calendar[12][31]
calendar[4]
: int*指针,指向数组calendar[4]
中下标为0的元素*(calendar[4] + 7)
:*(*(calendar+4) + 7)
:calendar
: int(*p)[31]类型—指向一个拥有31个整形元素数组的指针;int *day = &calendar
: calendar[0][0]
3.9 整数溢出
有符号数和无符号数结合时,有符号数会被转换为无符号数
4. 连接
4.2 声明和定义
如果两个源文件无意定义了相同的全局变量,系统应该能够处理这种情况。
但是若本意该全局变量只在该文件中使用,建议使用static
修饰。
static
同样可以修饰函数,表示该函数只在该函数体内生效。
4.3 形参和实参
一些编译器似乎支持这样的定义和声明:
/* 声明 */
int isvowel();
/* 定义 */
int isvowel(char c)
{
return c == 'a' || c == 'e';
}
声明中省略了形参类型,调用时,传参会默认为int类型。
虽然这个做法可能是可以的,但绝对不推荐这么做。
4.5 全局变量类型检查
int a = 5;
extern long a;
上面这种写法可以称之为错误,但大多数编译器无法检测出这种错误,或者自作聪明使用一个空间存储两个实例。
类似的:
char filename[] = "/etc/passwd";
extern char *filename;
这两种无法以一个合乎情理的方式共存,需要改为:
char filename[] = "/etc/passwd";
extern char filename[];
char *filename = "/etc/passwd";
extern char *filename;
5. 库函数
/* 看完UNIX环境编程再看 */
6. 预处理
6.1 宏定义中的空格
#define f(x) ((x)-1)
#define f (x) ((x)-1)
上面第二种写法能够达到预期的效果吗?答案是不能。其表达的含义是f
代表 (x) ((x)-1)
.
但调用时f(3)和f (3)
都能达到预期的效果.
6.4 宏不是类型定义
考虑下面的代码
#define T1 struct foo *
typedef struct foo *T2;
上面两种写法T1和T2从概念上完全符合,都是指向结构foo的指针
T1 a;
T2 b;
但是当试图声明多个变量时,问题就来了
T1 a, b;
T2 a, b;
第一个声明会被扩展为:
struct foo *a, b;
一个为指针类型,另一个不是.