《C陷阱与缺陷》Finux_you读书笔记(1)

p7,<优先级问题>:

以下循环语句的本意是跳过文件中的空格符、制表符和换行号:

while(c = ' ' || c == '\t' || c == '\n')

c = getc(f);

由于把==误写为=,实际上此语句将以下表达式的值付给了c:

'' || c == '\t' || c == '\n'

因为赋值运算符=的优先级要低于逻辑运算符||。


p8,<词法分析的贪心法>:

编译器将程序分解为符号的方法是:从左到右一个字符一个字符地读入,如果该字符能够组成一个符号,那么再读入下一个字符。判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复以上判断,直到读入字符组成的字符串不再有意义。

依据“贪心法”,下面的表达式将有以下解读:

a---b               ->    (a--) - b

a+++++b       ->     ((a++)++) + b

以下代码出现不符合程序员本意的错误:

y = x/*p    /*p指向的数*/;


p10,<整型常量>:

有时候上下文为了格式对齐的需要,可能无意中将十进制数写成八进制:

045

046

123


p11,<字符串>:

双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该指针被双引号里面的字符和最后结尾的'\0'初始化。so,以下程序也可以运行,打印出字符e:

 


p128,<嵌套注释>:

C语言定义不允许嵌套注释。

嵌套注释最大的好处就是暂时移除一块代码。但是如果用单色打印稿或没有语法着色功能的编辑器看代码时会让人注意不到代码已经移除了。

在实际工作中我看到有人用#if 0 ……#endif来暂时移除一大段代码。

ps:VIM注释大量代码的方法:

移动光标到开始注释的行;Ctrl+V进入可视化列选择模式;移动光标到最后需要注释的行(可以用num+G);按I进入插入模式;插入注释符;Esc退出。反注释选中注释符后按d。


p15-19,<难搞的声明>:

1、任何C变量的声明都由两部分组成:1 类型。2 一组类似表达式的声明符。对声明符求值返回声明中给定类型的结果。如:

float f;    这个声明的含义是:对f求值时,表达式f的类型为浮点数类型。

float *pf;    这是声明的含义是:*pf是一个浮点数,即pf是一个指向浮点数的指针;

float *g();   由于()的结合优先级高于*,*g()也就是*(g()):g是一个函数,返回值的类型是一个指向浮点数的指针。

float (*h)();    h是一个指针(函数指针),指向一个返回值类型为浮点数的函数。

2、我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:把变量声明中的变量名和声明末尾的分号去掉,剩余的部分用口号封装。如:

(float (*)())表示一个指向返回值为浮点类型的函数指针的类型转换符。

3、某程序员需要计算机启动时,硬件调用首地址为0位置的子例程的语句:

( * ( void ( * ) ( ) ) 0 ) ( );

分析:假定fp是一个函数指针,那么调用fp指向的函数方法为:(*fp)(); ANSI C标准允许将上式简写为:fp();但只是简写形式。所以实际上,上式这个牛b的语句中的( void ( * ) ( ) ) 0就相当于fp。也就是说:( void ( * ) ( ) ) 0是一个函数指针,上式调用了它指向的函数(地址0处的函数)。0前面的( void ( * ) ( ) )是一个类型转换符,转换0为一个函数指针,该指针指向一个返回值类型为void的函数。如果用typedef来表述,那就是:

typedef void (*fucptr)();

(*(fucptr)0)();

4、还有更头疼的,库函数signal的声明:

void ( *signal( int, void( * ) ( ) ) ) ( int );

分析:signal函数接收两个参数:一个代表被捕信号的整数值,一个指向用户提供的信号处理函数的指针。signal的返回值类型是指向用户提供的信号处理函数的指针,该函数返回值为void,参数为一个整形数。

这个声明在C标准库中用的是以下方法:

typedef void _Sigfun(int);

_Sigfun *signal(int, _sigfun *);

当然以下方法也可以:

typedef void (*HANDLER)(int);

HANDLER signal(int, HANDLER);


p19-23<神奇的优先级>:

1、书上说了好多,我认为优先级不用全部记住,记住以下顺序即可:

( () [] -> . ) > 单目运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 逻辑运算符 > (条件运算符) > 赋值运算符

2、单目运算符( ! ++ -- (type) * & sizeof ),是自左至右结合: *p++会被解释成:*(p++),即取指针p所指向的对象,然后p递增1;而不是(*p)++,即取指针p所指向的对象,然后该对象自增1.

3、分析如下语句:

tax_rate = income > 4000 && residency < 5 ? 3.5 : 2.0;

答案是:

tax_rate = ( ( (income > 4000) && (residency < 5) ) ? 3.5 : 2.0 );

4、经典错误:

(1)已定义的常量FLAG是一个整数,且这个整数值的二进制表示中只有某一位是1,即该整数是2 的某次幂。如果对于整形变量flag,需要判断它在常量FLAG为1的那一位上是否同样为1,有人这么写:

if(flag & FALG != 0);

实际上此语句被解释为:

if(flag & (FALG != 0));

(2)hi和low是两个整数,它们的值介于0与15之间。r是一个8位整数,且r的低四位与low各位上的数一致,r的高四位与hi的各位上的数一致,很自然会想到这样写:

r = hi<<4 + low;

但是此语句被解释为:

r = hi<< (4 + low);

(3)下面的循环语句本意是复制一个文件到另一个文件:

while(c = getc(in) != EOF)

putc(c, out);

但是却被解释为:

while(c = (getc(in) != EOF))

putc(c, out);

(4)一下代码本意是首先赋值给t,然后判断t是否等于STATY或者UNIONTY:

if(  ( t = BTYPE(pt1->aty) == STRTY )  ||  t == UNIONTY  );

但编译器却认为是:

if(   (  t = ( BTYPE(pt1->aty) == STRTY )  )  ||  t == UNIONTY   );


2011-02-17 21:49:24

p24,<该死的分号>:

要知道,有时候语句后面多一个或少一个分号,编译器是发觉不了的。

所以有时候程序没有按你想象的那样运行,检查分号吧!!


p28,<switch>:

用switch时,最需要注意的就是有没有忘记每个case后的break。除非有这样的代码:它的作用是编译器在查找符号时跳过程序中的空字符,空格键、制表符、换行符的处理都是相同的,除了遇到换行符时程序的代码行数计数器需要递增:


p33-39<指针啊指针,数组啊数组>:

1、C语言中的数组值得注意的是:

(1)C语言中只有一维数组,数组的大小必须在编译器就作为一个常数确定下来。

(2)对数组只能做两件事:确定数组大小;获得指向该数组下标为0的元素的指针。其他相关操作实际上都是通过指针。

2、一些基础:

int *ip;

int a[3];

ip+1指向的是内存中下一个整数,在大多数现代计算机中,它都不同于ip所指向地址的下一个内存位置。

在应该出现指针的地方用数组名替换,数组名就被当作指向该数组下标为0的元素的指针

(除了siziof(a),它代表的是整个数组a所占的字节数:sizeof(int)*3),因此如果这样写:

p = a;

就会把a中下标为0的元素的地址赋值给p。如果写成:

p = &a;

在ANSI C中是非法的,因为&a是一个指向数组的指针,与p的类型不匹配。

*a是对数组a下标为0的元素的引用。*(a + i)是数组下标为i的元素的引用,简记为a[i]。

3、关于二维数组:

int calendar[12][31];

int *p;

int i;

calendar[4] 代表了calendar数组的第5个元素,是一个有31个整形数的数组。

p = calendar[4]; 使得指针p指向了数组calendar[4]中下标为0的元素。

以下三句效果相同:

i = calendar[4][7];

i = *(calendar[4] + 7);

i = *(*(calendar + 4) + 7);

p = calendar 是非法的,类型不符,你懂的。calendar是指向数组的指针。

构造数组指针的方法:

int (*ap)[31];

上句表示(*ap)是一个拥有31个整型数的数组。ap是一个指向该数组的指针。因为此时可这样做:

ap = calendar;

4、一个例子:

int clendar[12][31];

此时我们需要清空clendar数组,用下标形式可以这样做:

用指针形式我们可以这样做:


p39-41<非数组指针>:

指的是字符串常量。

假定有两个字符串s和t,我们想将这两个字符串连接成单个字符串r,有人写了如下代码:

以上代码当然有错误:

(1)不能明确r指向何处。

(2)r指向的地址处应该有内存空间容纳字符串。

于是经过修改得出如下代码:

以上要求s、t的大小不是很大。但如果s、t都很小,又浪费了存储空间。为了能有合适的空间容纳,修改如下:

以上代码不足之处在于:

(1)没有检查malloc调用成功与否。

(2)时刻记住:malloc与free应该是配对使用的。

(3)申请的内存似乎不够用。因为字符串结尾还要有个'\0'。

修改为以下代码:

 


2011-02-18 15:14:33

p45-55<不对称边界和边界计算>:

1、用第一个入界点和第一个出界点来表示一个数值范围,好处是:

(1)取值范围的大小就是上界与下界之差。如数组的上届(第一个界点)恰好是数组元素的个数。

(2)如果取值范围为空,那么上界等于下界。

2、另一种不对称边界的考虑方式是:把上界看做是一个序列中第一个被占用的元素,下界看做序列中第一个释放的元素。

3、实现一个函数(bufwrite()),该函数功能是将长度无规律的输入数据送入缓冲区(一个能够容纳N个字符的内存)中去,每当这块内存被填满时,就将缓冲区的内容写出(flushbuf())。该函数有两个参数,第一个是一个指针,指向将要写入缓冲区的第一个字符;第二个参数是一个整数,代表将要写入缓冲区的字符数。程序及其测试如下:

 

下是一个效率更高的bufwrite():

 


2011-02-20 16:12:55

posted @ 2011-02-17 11:03  Finux_you  Views(331)  Comments(0Edit  收藏  举报