第1章 词法“陷阱”
“符号”(token)指的是程序的一个基本组成单元。
在C语言中,符号之间的空白(包括空格符、制表符或换行符)将被忽略。
1.1 =不同于==
把比较运算误写成赋值运算。
应该显式地进行比较。
if ((x=y) != 0)
foo();
如果把复制运算误写成比较运算,同样会造成混淆。
1.2 & 和 | 不同于 && 和 ||
1.3 词法分析中的“贪心法”
准二义性问题
1.4 整型常量
如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。因此,10与010的含义截然不同。
1.5 字符与字符串
用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。
用双引号引起的字符串,代表的确实一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化。
第2章 语法“陷阱”
2.1 理解函数声明
2.2 运算符的优先级问题
2.3 注意作为语句结束标志的分号
一个重要的例外是在if或者while语句之后需要紧跟一条语句时,如果此时多了一个分好,那么原来紧跟在if或者while子句之后的语句就是一条单独的语句,与条件判断部分没有了任何关系。
当一个声明的结尾紧跟一个函数定义时,如果声明结尾的分号被省略,编译器可能会把申明的类型视作函数的返回值类型。
2.4 switch语句
2.5 函数调用
C语言要求:在函数调用时计时函数不带参数也应该包括参数列表。
2.6 “悬挂”else引发的问题
else始终与同一对括号内内最近的为匹配的if结合。
第3章 语义“陷阱”
3.1 指针和数组
1. C语言中只有以为数组,而且数组的大小必须在编译器就作为一个常熟确定下来。(C99标准允许变长数组(VLA),GCC编译器中实现了变长数组)
2.
*(a+i)简记为a[i]
3.2 非数组的指针
char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);
if(!r) {
complain();
exit(1);
}
strcpy(r, s);
strcat(r, t);
free(r);
3.3 作为参数的数组声明
3.4 避免“举偶法”
混淆指针与指针所指向的数据。
3.5 空指针并非空字符串
当常熟0被转换为指针使用时,这个指针绝对不能被解除引用。
3.6 边界计算与不对称边界
内容较多
3.7 求值顺序
运算符&&和运算符||首先对左侧操作数求值,只在需要时才对右侧操作数求值。
3.8 运算符&&、||和!
3.9 整数溢出
当两个操作数都是有符号整数时,“溢出”就有可能发生,而且“溢出”的结果是未定义的。当一个运算的结果发生“溢出”时,做出任何假设都是不安全的。
一种正确的方式是将两者都强制转换为无符号整数:
if ((unsigned)a + (unsigned)b > INT_MAX)
complain();
INT_MAX代表可能的最大整数值,ANSI C标准在<limits.h>中定义。
3.10 为函数main提供返回值
第4章 连接
4.1 什么是连接器
4.2 声明与定义
extern int a;
上述声明是对外部变量a的引用,而不是对a的定义。
同时程序就必须在别的某个地方包括语句
int a;
这两个语句既可以是子啊同一个源文件中,也可以位于程序的不同源文件之中。
严格的规则是,每个外部变量只能够定义一次。(如果违反该规则,产生的结果与系统有关。)大多数系统都会拒绝接收这种程序。
4.3 命名冲突与static修饰符
4.4 形参、实参与返回值
4.5 检查外部类型
4.6 头文件
第5章 库函数
第6章 预处理器
第7章 可移植性缺陷
第8章 建议与答案
附录B 访谈
Koenig:就其本省而言,C++是一种非常低级的语言。唯有利用库,才能写出高层次的程序来。