1、C陷阱与缺陷笔记在初读C陷阱与缺陷时,前几章介绍的问题经常遇到,较容易掌握。又因懒于动手未做笔记,越到后面越觉得自己记忆力有限才补做笔记。毕竟好记性不如烂笔头。前四章时在别的笔记中粘贴过来。后面几章的学习中遇到似是而非的问题也参照了其他人得笔记并整理出自己的东西。现在一并发出来,为更多的人提供参考。让我们一起坚定的走下去!第1章 词法“陷阱”1.1 不同于 为比较运算符, 为赋值运算符例:while( c = | c = t | c = n ) c = getc( f ); 本意是c和 比较,但错用成赋值符。这样的后果是将 | c = t | c = n 这个表达式的值给了c, 而使c = 1
2、。 同样: if ( ( filedesc = open( argvi, 0 ) ) (*0)(); - (*(void (*)()0)(); 2.2 运算符的优先级问题 优先级最高者其实并不是真正意义上的运算符,包括:数组下标,函数调用操作符各结构成员选择操作符。他们都是自左于右结合,因此 a.b.c的含义是(a.b).c。 () - . 单目运算符的优先级仅次于前述运算符。在所有的真正意义上的运算符中,它们的优先级最高。单目运算符是自右至左结合。因此*p+会被编译器解释成*(p+)。 ! + = = (type) * & sizeof 优先级比单目运算符要低的,接下来就是双目运算符。在双目
3、运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。 * / % + - = = != & | & | ?:我们需要记住的最重要的两点是:1.任何一个逻辑运算符的优先级低于任何一个关系运算符。2.移位去处符的优先级比算术运算符要低,但是比关系运算符要高。 2.3 主义作为语句结束标志的分号 2.4 关于switch语句 case : linecount+; case : case : . 2.5 函数调用 f();是一个函数调用语句,而f; 计算函数f的地址,却并不调用该函数。 2.6 “悬挂”else引发的问题 if (x =
4、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的元素的指针。其他有关数组的操作,
5、哪怕他们看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。 很多程序设计语言中都内建有索引运算,在C语言中索引运算是以指针算术的形式来定义的。 如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1,就能够得到指向该数组中下一个元素的指针。同样地,如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针。 int calendar1231; int *p; 则p = calendar; 是非法的。因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用
6、calendar名称会将其转换为一个指向数组的指针;而p是一个指向整型变量的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针。 要构造一个指向数组的指针的方法: int calendar1231; int (*monthp)31; monthp = calendar; 这样,monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一。 3.2 非数组的指针 在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符()的内存区域的地址。 假定我们有两个字符串s和t,我们希望将这两个字符串连接成单个字符串t。 考虑: c
7、har *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 作
8、为参数的数组声明 在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。 因此,将数组作为函数参数毫无意义。所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。 3.4 避免“举隅法” 需要记住的是,复制指针并不同时复制指针所指向的数据。 3.5 空指针并非空字符串 出了一个重要的例外情况,在C语言中将一个整型转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊的情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。 #define Null 0 需要记住的重要一点是,当
9、常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference)。 换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。 3.6 边界计算与不对称边界 在所有常见的程序设计错误中,最难于察觉的一类是“栏杆错误”,也常被称为“差一错误”(off-by-one error)。 避免“栏杆错误”的两个通用原则: (1) 首先考虑最简单情况下的特例,然后将得到的结果外推。 (2) 仔细计算边界,绝不掉以轻心。 用第一个入界点和第一个出界点来表示一个数值范围 能够降低这类错误发生的可能性。 比如整数x满足边界条件x=16且x=16且x38,这里下界是“入界点”,即包括在取值范围之中;而上界是“出界点”,即不包括在取值范围之中。 另一种考虑不对称边界的方式是,把上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。 3.7 求值顺序 C语言
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1