C陷阱与缺陷(学习笔记)

前言

掌握细节并不难,难的是如何运用之妙!

词法“陷阱”

词:单词
符号=作为赋值运算,是因为操作频繁,书写简单

词法分析中的“贪心法”

  1. a---b与表达式a -- - b的含义相同,而与a - -- b的含义不同
  2. y = x/*p与y = x / *p不同(第一个/*被理解为注释符)

理解:这也许就是编码规范要求操作符两侧添加合理空格的原因之一吧

字符与字符串

  1. 用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针。
  2. 字符’\0’对应的十进制是0

语法“陷阱”

运算符优先级

括号(),数组下标[],函数调用,结构成员选择操作符->/. > 单目运算符 > 算数运算符 > 移位运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符 > 条件运算符(三目运算符)

  1. *p++,先计算p++
  2. while (c=getc(in) != EOF)和while ((c=getc(in)) != EOF)

switch语句

  1. case语句结束时不加break的场景,最好加上注释// fall-through

练习题:

  1. 为什么C语言允许初始化列表中出现多余的逗号,如:
int a[] = {
    1, 
    2,
};

enum {
	a,
	b,
};

据说是为了简化代码生成工具的逻辑处理。

语义“陷阱”

指针与数组

C语言中的数组值得注意的地方有以下两点:

  1. C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,C语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
    (备注:C99标准允许变长数组(VLA))
  2. 对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为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语言中只有四个运算符(&&、||、?: 和,)存在规定的求值顺序。

  1. 运算符&&和||首先对左侧操作数求值,只在需要时才对右侧操作数求值
  2. 运算符?:有三个操作数:在a?b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值
  3. 逗号运算符,首先对左侧操作数求值,然后该值被”丢弃“,再对右侧操作数求值。

整数溢出

”溢出“,是指有符号整数算术运算中发生的现象

预处理器

不能忽视宏定义中的空格

#define f (x)  ((x)-1)
#define f(x)   ((x)-1)

宏并不是函数

  1. #define max(a,b) ((a)>(b) ? (a) : (b)),每个参数用括号括起来,整个结果表达式也用括号括起来
  2. max(a++, b),参数不能出现副作用,a会被计算两次
  3. 避免宏展开成庞大的表达式,如:max(a,max(b,max(c,d)))

宏并不是语句

assert宏

  1. #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重新组队。

  1. #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之前的分号是一个语法错误

  1. #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)));

理解

  1. if, else代码块加{},所以代码规范一般要求,即便只有一条语句的代码块也要加{}
  2. 根据宏具体作用定义形式,有的需要定义成表达式,有的需要定义成语句块(用do{ } while(0)包裹宏内容)

宏并不是类型

#define T1 struct foo *
typedef struct foo * T2;

T1 a, b;  // 扩展为:struct foo * a, b; 一个是指针变量,一个是结构体变量
T2 a, b;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值