前言
掌握细节并不难,难的是如何运用之妙!
词法“陷阱”
词:单词
符号=作为赋值运算,是因为操作频繁,书写简单
词法分析中的“贪心法”
- a---b与表达式a -- - b的含义相同,而与a - -- b的含义不同
- y = x/*p与y = x / *p不同(第一个/*被理解为注释符)
理解:这也许就是编码规范要求操作符两侧添加合理空格的原因之一吧
字符与字符串
- 用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针。
- 字符’\0’对应的十进制是0
语法“陷阱”
运算符优先级
括号(),数组下标[],函数调用,结构成员选择操作符->/. > 单目运算符 > 算数运算符 > 移位运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符 > 条件运算符(三目运算符)
- *p++,先计算p++
- while (c=getc(in) != EOF)和while ((c=getc(in)) != EOF)
switch语句
- case语句结束时不加break的场景,最好加上注释// fall-through
练习题:
- 为什么C语言允许初始化列表中出现多余的逗号,如:
int a[] = {
1,
2,
};
enum {
a,
b,
};
据说是为了简化代码生成工具的逻辑处理。
语义“陷阱”
指针与数组
C语言中的数组值得注意的地方有以下两点:
- C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,C语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
(备注:C99标准允许变长数组(VLA)) - 对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。
边界计算与不对称边界
“栏杆错误”,也常被称为”差一错误“(off-by-one error)
问题:100英尺长的围栏每隔10英尺需要一根支撑用的栏杆,一共需要多少根栏杆?
解决方法:用第一个入界点和第一个出界点来表示一个数值范围,即[a, b)
如:x>=16且x<=37,表示成:x>=16且x<38
这个问题,还会出现在:队列,栈等缓冲区操作中
ANSI C标准明确允许这种用法:数组中实际不存在的”溢界“元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较。
如:
#define N 1024
static char buffer[N];
static char *bufptr;
bufptr = buffer;
……
if (bufptr == &buffer[N])
求值顺序
C语言中只有四个运算符(&&、||、?: 和,)存在规定的求值顺序。
- 运算符&&和||首先对左侧操作数求值,只在需要时才对右侧操作数求值
- 运算符?:有三个操作数:在a?b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值
- 逗号运算符,首先对左侧操作数求值,然后该值被”丢弃“,再对右侧操作数求值。
整数溢出
”溢出“,是指有符号整数算术运算中发生的现象
预处理器
不能忽视宏定义中的空格
#define f (x) ((x)-1)
#define f(x) ((x)-1)
宏并不是函数
- #define max(a,b) ((a)>(b) ? (a) : (b)),每个参数用括号括起来,整个结果表达式也用括号括起来
- max(a++, b),参数不能出现副作用,a会被计算两次
- 避免宏展开成庞大的表达式,如:max(a,max(b,max(c,d)))
宏并不是语句
assert宏
#define assert(e) if (!e) assert_error(__FILE__, __LINE__)
例子:
if (x > 0 && y > 0)
assert(x > y);
else
assert(y > x);
展开后:
if (x > 0 && y > 0)
if (!(x > y)) assert_error("foo.c", 37);
else
if (!(y > x)) assert_error("foo.c", 39);
问题:出现了if和else重新组队。
#define assert(e) { if (!e) assert_error(__FILE__, __LINE__); }
展开后:
if (x > 0 && y > 0)
{ if (!(x > y)) assert_error("foo.c", 37) };
else
{ if (!(y > x)) assert_error("foo.c", 39) };
问题:else之前的分号是一个语法错误
#define assert(e) ((void)((e) || assert_error(__FILE__, __LINE__)))
展开后:
if (x > 0 && y > 0)
((void)((x > y)) || assert_error("foo.c", 37)));
else
((void)((y > x)) || assert_error("foo.c", 39)));
理解:
- if, else代码块加{},所以代码规范一般要求,即便只有一条语句的代码块也要加{}
- 根据宏具体作用定义形式,有的需要定义成表达式,有的需要定义成语句块(用do{ } while(0)包裹宏内容)
宏并不是类型
#define T1 struct foo *
typedef struct foo * T2;
T1 a, b; // 扩展为:struct foo * a, b; 一个是指针变量,一个是结构体变量
T2 a, b;