《C陷阱与缺陷》是人民邮电出版社2008年出版的书籍,作者是(美)凯尼格。本书主要内容是从多个方面分析了C编程中可能遇到的问题。
词法分析中的“贪心法”
C语言的某些符号,例如/、*\和=
,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/*和==
,以及标识符,包括了多个字符,称为多字符符号。C语言对读入字符做出了一个很简单的规则,贪心读取。将程序分解成符号的方法:从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分,如果可以,继续读入下一个字符。
a+++++b
(a++) + (++b)
字符与字符串
单引号括起来的一个字符代表一个整数,而用双引号括起来的一个字符代表一个指针,切不可混用
printf('\n');
编译时会生成一条错误信息,因为'\'并不是一个字符指针。
运算符优先级
优先级最高者其实并不是真正意义上的运算符,包括:数组下标、函数调用操作符各结构成员选择操作符。结合性自左向右。
单目运算符的优先级仅次于上述运算符函数指针(*p)() 指针函数 *p()
优先级比单目运算符低的是,双目运算符。其中算术运算符最高,移位运算符次之,关系运算符、逻辑运算符、赋值运算符。
任何一个逻辑运算符的优先级低于关系运算符
移位运算符的优先级比算术运算符要低,但比关系运算符要高。
三目运算符进高于赋值运算符。
悬挂else引发的问题
if (x == 0)
if (y == 0)
exit(0);
else
{
z = x + y;
}
C语言有这样的规则:else始终与同一对括号内最近的未匹配的if结合。
C语言允许初始化列表中出现多余的逗号
int days[] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31,};
为什么这种特性是有用的?
我们把上例的缩排格式稍作改动
int days[] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31,
};
我们可以很容易看出,初始化列表的每一行都是以逗号结尾的,每一行在语法上的相似性,自动化的程序设计工具才很够很方便地处理很大的初始化列表。
指针和数组
数组值得注意的两点:
- C语言只有一维数组,而且数组的大小必须在编译期就作为一个常数确切定下来,然而C语言中数组的元素可以是任何类型的对象。
- 对于一个数组我们只能做两件事:确定数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,实际上都是通过指针操作的。
非数组指针
在C语言中,字符串常量代表了一块包括字符串所有字符以及一个空字符’\0’的内存区域的地址。
返回整数的getchar
#include <stdio.h>
int main(void)
{
char ch;
while ((ch = getchar()) != EOF)
{
putchar(ch);
}
return 0;
}
getchar是读入函数的一种。它从标准输入里读取下一个字符,相当于getc(stdin)。返回类型为int型,为用户输入的ASCII码或EOF。
原因在于程序中的变量c被声明为char类型,而不是int类型,这意味着无法容下所有可能的字符,特别是无法容下EOF。
因此,最终结果存在两种可能。一种可能是,某些合法的输入字符在被“截断”后使得ch的取值与EOF相同;另一种可能是,ch根本取不到EOF这个值,对于前一种情况,程序将在文件复制的中途停止,后一种情况陷入死循环。
某些编译器会对函数getchar的返回值作了“截断处理”,并把低端字节赋给变量c。