《C陷阱与缺陷》笔记.docx
- 文档编号:2130271
- 上传时间:2022-10-27
- 格式:DOCX
- 页数:16
- 大小:30.22KB
《C陷阱与缺陷》笔记.docx
《《C陷阱与缺陷》笔记.docx》由会员分享,可在线阅读,更多相关《《C陷阱与缺陷》笔记.docx(16页珍藏版)》请在冰豆网上搜索。
《C陷阱与缺陷》笔记
在初读《C陷阱与缺陷》时,前几章介绍的问题经常遇到,较容易掌握。
又因懒于动手未做笔记,越到后面越觉得自己记忆力有限才补做笔记。
毕竟好记性不如烂笔头。
前四章时在别的笔记中粘贴过来。
后面几章的学习中遇到似是而非的问题也参照了其他人得笔记并整理出自己的东西。
现在一并发出来,为更多的人提供参考。
让我们一起坚定的走下去!
第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语言中的单引号和双引号含义迥异,在某些情况下如果把两者弄混,编译器并不会检测报错,从而在运行是产生难以预料的结果。
用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。
用双引号引起的字符串,代表的却是一个指向无名数字起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制为零的字符''初始化。
然而,某些C编译器对函数参数并不进行类型检查,特别是对printf函数的参数。
因此,如果用
printf('');
来代替正确的
printf("");
则会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。
整型数(一般为16位或32为)的存储空间可以容纳多个字符(一般为8位),因此有个C编译器允许在一个字符常量(以及字符串常量)中包括多个字符。
也就是说,用'yes'代替"yes"不会被该编译器检测到。
后者的含义是“一次包括'y''e''s'以及空字符''的4个连续内存单元的首地址“。
前者的含义并没有准确的进行定义,但大多数编译器理解为,“一个整数值,由'y''e''s'所代表的整数值按照特定编译器实现中定义的方式组合得到“。
(注:
在BorlandC++v5.5和LCCv3.6中采取的做法是,忽略多余的字符,最后的整数值即第一个字符的整数值;而在VisualC++6.0和GCCv2.95中采取的做法是,依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。
)
第2章:
语法“陷阱”
2.1理解函数声明
(*(void(*)())0)();
任何复杂表达式其实只有一条简单的规则:
按照使用的方式来声明。
任何C变量的声明都由两部分组成:
类型以及一组类似表达式的声明符(declarator)。
声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定类型的结果。
因为声明符与表达式的相似,所以我们也可以在声明符中任意使用括号:
float((f));
这个声明的含义是:
当对其求值时,((f))的类型为浮点类型,由此可以推知,f也是浮点类型。
各种形式的声明还可以组合起来,就像在表达式中进行组合一样。
因此,
float*g(),(*h)()表示*g()与(*h)()是浮点表达式。
因为()结合优先级高于*,*g()也就是*(g()):
g是一个函数,该函数的返回值类型为指向浮点数的指针。
同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。
一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:
只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。
例如,因为下面的声明:
float(*h)();
表示h是一个指向返回值为浮点类型的函数的指针,因此,(float(*)())表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。
(*fp)();->(*0)();->(*(void(*)())0)();
2.2运算符的优先级问题
优先级最高者其实并不是真正意义上的运算符,包括:
数组下标,函数调用操作符各结构成员选择操作符。
他们都是自左于右结合,因此a.b.c的含义是(a.b).c。
()[]->.
单目运算符的优先级仅次于前述运算符。
在所有的真正意义上的运算符中,它们的优先级最高。
单目运算符是自右至左结合。
因此*p++会被编译器解释成*(p++)。
!
~++===(type)*&sizeof
优先级比单目运算符要低的,接下来就是双目运算符。
在双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。
*/%
+-
<<>>
<<=>>=
==!
=
&
^
|
&&
||
?
:
我们需要记住的最重要的两点是:
1.任何一个逻辑运算符的优先级低于任何一个关系运算符。
2.移位去处符的优先级比算术运算符要低,但是比关系运算符要高。
2.3主义作为语句结束标志的分号
2.4关于switch语句
case'':
linecount++;
case'':
case'':
.......
2.5函数调用
f();是一个函数调用语句,而f;计算函数f的地址,却并不调用该函数。
2.6“悬挂”else引发的问题
if(x==0)
if(y==0)error();
else{
z=x+y;
f(&z);
}
else与最近的if配对。
除非用括号进行划分区域。
第3章“语义”陷阱
3.1指针和数组
C语言中的数组值得注意的地方有以下两点:
1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。
然而,C语言中数组的元素可以是任何类型的对象,当然也就可以是另外一个数组。
(注:
C99标准允许变长数组(VLA)。
GCC编译器中实现了变长数组,但细节与C99标准不完全一致。
)
2.对于一个数组,我们只能够做两件事:
确定该数组的大小,以及获得指向该数组下标为0的元素的指针。
其他有关数组的操作,哪怕他们看上去是以数组下标进行运算的,实际上都是通过指针进行的。
换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。
很多程序设计语言中都内建有索引运算,在C语言中索引运算是以指针算术的形式来定义的。
如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1,就能够得到指向该数组中下一个元素的指针。
同样地,如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针。
intcalendar[12][31];
int*p;
则p=calendar;是非法的。
因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针;而p是一个指向整型变量的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针。
要构造一个指向数组的指针的方法:
intcalendar[12][31];
int(*monthp)[31];
monthp=calendar;
这样,monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一。
3.2非数组的指针
在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符('')的内存区域的地址。
假定我们有两个字符串s和t,我们希望将这两个字符串连接成单个字符串t。
考虑:
char*r,*malloc();
r=mallor(strlen(s)+strlen(t));
strcpy(r,s);
strcat(r,t);
这个例子的错误有3点:
1,malloc函数有可能无法提供请求的内存。
2,显式地分配了内存必须显式地释放内存。
3,malloc函数并未分配足够的内存。
正确是方法:
char*r,*malloc();
r=malloc(strlen(s)+strlen(t)+1);
if(!
r){
complain();
exit
(1);
}
strcpy(r,s);
strcat(r,t);
/*一段时间之后*/
free(r);
3.3作为参数的数组声明
在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。
如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。
因此,将数组作为函数参数毫无意义。
所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。
3.4避免“举隅法”
需要记住的是,复制指针并不同时复制指针所指向的数据。
3.5空指针并非空字符串
出了一个重要的例外情况,在C语言中将一个整型转换为一个指针,最后得到的结果都取决于具体的C编译器实现。
这个特殊的情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。
#defineNull0
需要记住的重要一点是,当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference)。
换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。
3.6边界计算与不对称边界
在所有常见的程序设计错误中,最难于察觉的一类是“栏杆错误”,也常被称为“差一错误”(off-by-oneerror)。
避免“栏杆错误”的两个通用原则:
(1)首先考虑最简单情况下的特例,然后将得到的结果外推。
(2)仔细计算边界,绝不掉以轻心。
用第一个入界点和第一个出界点来表示一个数值范围能够降低这类错误发生的可能性。
比如整数x满足边界条件x>=16且x<=37我们可以说x>=16且x<38,这里下界是“入界点”,即包括在取值范围之中;而上界是“出界点”,即不包括在取值范围之中。
另一种考虑不对称边界的方式是,把上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。
3.7求值顺序
C语言
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C陷阱与缺陷 陷阱 缺陷 笔记