《C陷阱与缺陷》学习笔记.docx
《《C陷阱与缺陷》学习笔记.docx》由会员分享,可在线阅读,更多相关《《C陷阱与缺陷》学习笔记.docx(13页珍藏版)》请在冰豆网上搜索。
《C陷阱与缺陷》学习笔记
《C陷阱与缺陷(CTrapsandPitfalls)》学习笔记
(1)
书作者结合自己实际的编程经验,讲述程序员写C程序中会遇到的问题和麻烦(“陷阱”-程序没有按照程序员所期待的方法执行),教你如何避免遇到这些问题。
/*本文仅仅是读书笔记^_^总结理解书中的观点*作者:
nick@hitron*email:
lichuxin9801@*/
第1章词法“陷阱”
1.1=不同于==
首先,==为比较运算符而=为赋值运算符,这二个符号再实际使用中,容易写反,而造成的错误不易发现。
比如:
while(c=''||c=='\t'||c=='\n')
c=getc(f); 在c和''比较的时候,写错了。
这样的后果是将''||c=='\t'||c=='\n'这个表达式的值给了c,而使c=1; 同样:
if((filedesc==open(argv[i],0))<0)
error(); open的返回值和filedesc比较的结果只能是0或1,所以,error没有机会调用。
但是,此时filedesc的值于open返回值无关,编译器这里不会报错。
容易被忽视,达不到检查效果。
1.2&和|不同于&&和||
&和|均为按位运算符,而&&和||均为逻辑运算符,不能混淆。
1.3语法分析中的“贪心法”
C语言的某些符号,比如/*、=等,只有一个字符长,称为单字符符号,而/*==这些以及标识符,有多个字符,成为多字符符号。
C编译器读入字符的规则:
每个符号应该包含尽可能多的字符,即:
从左到右,一个个读入,如果能组成一个符号,则继续读入下一个,这样子下去,直到不能组成一个有意义的符号;这个处理策略有时候叫“贪心法”; 除了字符串和字符常量,符号中间不能有空白符(比如空格、制表、换行符); 如:
a---b与a---b相同,而与a---b不同。
这些准二义性的问题,要注意避免(如添加()等方法)。
1.4整型常量
如果一个整形常量的第一个字符是数字0,那么该常量将被视作八进制数。
因此,10和010是完全不同的含义。
此外书中还介绍了一些ANSIC不允许的做法,比如将8和9也作为八进制数字处理。
1.5字符和字符串
C语言中单引号和双引号的含义不同,使用错误时,有的编译器并不会检测报错,但是运行产生结果难以预料。
单引号引起的一个字符实际上代表一个整数,数值对应于该字符在编译器上采用的字符集中的序列。
对于常用的采用ASCII字符集的编译器而言,'a'的含义与97(十进制)或0x61(十六进制)严格一致; 双引号引起字符串,代表一个指向无名数组的起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为0的字符'\0'初始化;比如:
printf("Hello\n");
与
charhello[]={'H','e','l','l','o','\n',0}; printf(hello);
等效。
《C陷阱与缺陷(CTrapsandPitfalls)》学习笔记
(2)
/************************************本文仅仅是读书笔记^_^总结理解书中的观点*作者:
nick@hitron*email:
lichuxin9801@************************************/
第二章:
语法“陷阱”
2.1理解函数声明
问题:
计算机启动时,硬件调用首地址为0位置的子程序,为了模拟开机启动时的情形,设计个C语句,显式的调用该子程序,语句是:
(*(void(*)())0)();
这类表达式有一个构造规则:
按照使用方式声明。
C变量的声明分两部分:
类型以及一组类似表达式的声明符。
对声明符求值应该返回一个声明中给定类型的结果:
比如:
floatfun();
/*表达式fun()求值的结果是一个浮点数,fun()是一个返回值为浮点类型的函数。
*/
float*pt;
/**pt()是一个浮点数,pf是一个指向浮点数的指针*/
组合起来也是可以的:
float*fun(),(*h)();
/**g()和(*h)()是浮点表达式,*g()就是*(g()):
g是一个返回值为指向浮点数的指针的函数,h是一个函数指针,指向的函数返回值为浮点类型*/
结合上面的例子,(float (*)())表示一个"指向返回值为浮点类型的函数的指针"的类型转换符。
下面分析表达式(*(void(*)())0)();的含义:
(1)fp是一个函数指针,如何调用fp指向的函数呢:
(*fp)();
(2)将常数0转型为“指向返回值为void的函数的指针”类型,这样子写:
(void(*)())0;替换上面的分析结果,得到(*(void(*)())0)(); 使用typedef能更清晰的表达:
typedefvoid(*funcptr)(); (*(funcptr)0)(); 书中还有一个例子signal函数来显示这种用法:
void(*signal(int,void(*)(int)))(int); 用typedef简化:
typedefvoid(*HANDLER)(int); HANDLERsignal(int,HANDLER);
2.2运算符的优先级问题
具体的优先级列表查看书; 优先级最高的不是真正意义上的运算符,比如:
数组下标([]),函数调用操作符(()),各结构体成员选择符(.->),它们都是从左到右结合。
比如:
a.b.c,则是(a.b).c; 其次是单目运算符也叫一元运算符,在有意义的运算符中,他们的优先级最高,他们是从右向左结合的; 比如:
(1)*p()解释为*(p())
/*函数调用操作符优先级高于取值符**/
;
(2)*p++解释为*(p++),即取p指向的对象,然后将p递增1,而不是p指向的对象递增1((*p)++); 接着是双目运算符也叫二元运算符,其中,算术运算符优先级最高,再依次移位,关系,逻辑,赋值,条件运算符(三目)。
重要的是:
(1)逻辑运算符优先级低于任何关系运算符;
(2)移位运算符优先级比算术运算符低,但是比关系运算符高。
*、/、%优先级相同且高于+、-,位移<<、>>优先级一致,但是低于前2者; 比如:
1
/
2*a解释为(1
/
2)*a,因为*、
/
、%都是从左到右的;
对于关系运算符,==和!
=优先级低于其他,比如:
a
&,^,|这样的顺序; 运算符举例:
(1)tax_rate=income>40000&&residency<5?
3.5:
2.0;分析结果为:
tax_rate=((income>40000)&&(residency<5)3.5:
2.0);
(2)while(c=getc(int)!
=EOF)putc(c,out);分析结果为:
c=(getc(int)!
=EOF); (3)if((t=BTYPE(pt1->aty)==STRTY)||t==UNIONTY)分析结果为:
(t=(BTYPE(pt1->aty)==STRTY)))||t==UNIONTY;
2.3作为语句结束标志的分号
举例说明:
(1)if(x[i]>big)big=x[i];
/*如果加错了;放在if()后面,则必定执行big=x[i],和判断没关系;*/
(2)if(n<3)return;
logrec.data=x[0];
logrec.time=x[2];
logrec.code=x[3];
/*如果吧return后面的;忘了,返回的是return=logrec.data=x[0],这样子的bug很难找。
*/
(3)structlogrec{intdata;inttime;intcode;
};
main()
{……
}
/*如果遗漏了struct声明后面的;,上面的实际效果为声明main的返回值为structlogrec的类型*/
2.4switch语句
switch最容易忘记的是case后面有时候是需要break的,当然有时候不用break可以达到很特殊的效果。
比如:
写一段代码,作用是一个编译器再查找符号的时候跳过程序中的空白字符,包括空格,制表,换行符号。
到遇到换行符号的时候,计数器递增,代码如下:
case'\n':
linecount++; case'\t':
case'':
……
2.6函数的调用
函数调用的时候即使不带参数,也必须包括参数的列表,比如:
f();
/*调用函数*/
f;
/*计算函数的地址,啥也不干*/
2.7“悬挂”else引发的问题
if和else匹配的问题容易出错:
比如:
if(x==0)
if(y==0)error(); else{
z=x+y;
f(&z); } 程序本来意图是第一个if和else搭配的,这样子写是和else最近的一个if和它搭配了。
因此,最好要将if里面的内容用括号封装起来。
《C陷阱与缺陷(CTrapsandPitfalls)》学习笔记(3)
/************************************本文仅仅是读书笔记^_^总结理解书中的观点*作者:
nick@hitron*email:
lichuxin9801@************************************/
第三章:
语义“陷阱”
3.1指针和数组
C语言中数组值得注意一下2点:
(1)C语言中只有一维数组,而且数组的大小必须再编译期就作为一个常数确定下来,数组元素可以是任何类型对象,也可是另一个数组。
(C99允许遍长数组VLA,还没看过^_^)
(2)对于一个数组,只能有2种行为:
确定该数组的大小,以及获得指向该数组下标为0的元素的指针。
其他操作都是基于指针的,任何一个数组下标运算都等同于一个对应的指针运算。
比如:
(1)struct{ intp[4]; doublex;}b[17];
/*b是一个拥有17各元素的数组,每个元素都是一个结构……*/
(2)intcalendar[12][31];calendar是一个数组,有12个数组类型的元素,每个元素是一个拥有31个整型元素的数组。
因此求sizeof(calendar)的值是31*12*sizeof(int),(注意sizeof与st