《C陷阱与缺陷》读书笔记,包含各个章节的主要内容。
书籍封面

¶词法陷阱
- 程序中的单个字符孤立来看并没有什么意义,只有结合上下文才有意义。
=不同于==。=是赋值运算符,其运算结果是右边表达式的取值。==是比较运算符,其运算结果是0(不等)或1(相等)。&不同于&&。&是位与运算符,&&是逻辑与。|不同于||。|是位或运算符,||是逻辑或。- 词法分析中的贪心法:每个符号应该包含尽可能多的字符,符号的中间不能有空白(空格符、制表符和换行符)。
a---b和a -- - b的含义相同,而与a - -- b的含义不同。- 合理的利用空格和括号,进行符号分隔。
- C语言可以表示三种字面常量,以
0x开头的十六进制,以0开头的八进制,其余是十进制。注意每种进制下的有效字符集。 - 用单引号引起的一个字符实际上代表一个
整数,一般是ASCII。用双引号引其的字符串,代表的是一个指向无名数组起始字符的指针。
¶词法陷阱
¶理解函数声明
利用typedef简化包含函数指针的函数声明。
¶运算符的优先级问题
C语言运算符优先级表:TODO
优先级从低到高依次为:
- 数组下标、函数调用、结构成员选择,自左向右。
- 单目运算符
- 双目运算符
- 算术运算
- 移位运算
- 关系运算:
==和!=低于其他关系运算符 - 逻辑运算:从高到低依次为:& ^ | && ||
- 赋值运算
- 条件运算符(? :)
- 逗号运算符
¶注意作为语句结束标志的分号
- if语句和while语句的后面
不要加分号 - return语句、结构体和联合体定义的后面
要加分号。
¶switch语句
每个case语句最好都加上一个break语句,如果确实不用加,请注释说明。case作为入口,只在遇到break或switch结束时,退出switch语句。
¶函数调用
在函数调用时即使函数不带参数,也应该包括参数列表。
1 | f(); |
¶悬挂else引发的问题
else始终与同一对括号内最近的未匹配的if结合。
¶语义陷阱
本章列出了若干种可能引起歧义的程序书写方式。
¶指针与数组
C语言的数组应该注意以下两点:
- C语言只有一维数组,数组大小在编译期就作为一个常数确定。数组元素可以是任何类型的对象,可以仿真出多维数组。
- 对于一个数组,只能获取数组大小、获取指向数组下标为0的元素的指针。其余操作都是通过指针进行的。
考虑以下的数组,该数组拥有12个元素,每个元素都是一个拥有31个整型元素的数组。sizeof(calendar)的值是12*31*sizeof(int)。在其他场合,calendar会被转换成一个指向calendar数组的起始元素的指针,其类型是int(*)[31]。
1 | int calendar[12][31]; |
指针加1,表示指向下一个元素。
¶非数组的指针
¶作为参数的数组声明
使用数组名作为参数,那么数组名会立刻转换为指向该数组第1个元素的指针。
¶避免举隅法
复制指针并不同时复制指针所指向的数据。
¶空指针并非空字符串
¶边界计算与不对称边界
¶求值顺序
- a && b 如果a为假,则b不会求值
- a || b 如果a为真,则b不会求值
- a ? b : c 如果a为真,只有b会求值;否则只有c会求值
- a, b, c a、b、c顺序求值,a和b的值求值,c的值作为表达式的值
¶运算符&&、||和!
逻辑运算与按位运算的区别。
¶整数溢出
¶为函数main提供返回值
¶链接
一个C程序可能是由多个分别编译的部分组成,这些不同部分通过一个叫做链接器的程序合并成一个整体。
¶什么是链接器
¶声明与定义
¶命名冲突与static修饰符
static修饰符号的作用域仅在本文件。
¶形参、实参与返回值
¶检查外部类型
¶头文件
¶库函数
探讨某些常见的库函数,以及编程者在使用它们的过程中可能的出错之处。
¶返回整数的getchar函数
1 | int getchar(void); |
¶更新顺序文件
fwrite和fread之间需要调用fseek。
¶输出缓冲与内存分配
setbuf函数。
¶使用errno检测错误
先检查库函数的返回值,出错之后再检查errno。
¶库函数signal
信号处理函数是异步执行的。要足够的简单。
¶预处理器
- 修改程序中出现的所有实例。
- 降低调用开销,
¶不能忽视宏定义中的空格
¶宏并不是函数
¶宏并不是语句
如果宏包括多条语句,最好用do{ ... }while(0)包裹起来。
¶宏并不是类型定义
¶可移植性缺陷
¶应对C语言标志变更
¶标识符名称的限制
¶整数的大小
¶字符是有符号整数还是无符号整数
¶移位运算符
- 无符号数右移空出的位填充0,而有符号数填充0还是符号位,取决于实现。
- 移位的位数是整数的长度。