《C陷阱与缺陷》读书笔记

  1. <span class="Apple-style-span">在C语言中,符号中间的空白(包括空格符,制表符和换行符)将被忽略。</span>

  2. <span class="Apple-style-span">贪心法:C语言中,每一个符号应该包含尽可能多的字符。</span>

  3. <span class="Apple-style-span">如果一个整形常量的第一个字符为0,那么该常量将被视为八进制数。</span>

  4. <span class="Apple-style-span">用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器所采用的字符集中的序列值;而用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针,该数组被双引号中的字符以及一个额外的二进制为0的字符'\0'初始化。</span>

  5. <span class="Apple-style-span">!=的优先级比&高,加法运算符优先级比移位运算符高。</span>

  6. <span class="Apple-style-span">优先级表:</span> <!--more-->

  7. <span class="Apple-style-span">优先级助记:</span>

    1. <span class="Apple-style-span">优先级最高的并不是真正意义上的运算符,包括数组下标,函数调用操作符,结构成员选择操作符。他们都是自左至右结合。</span>

    2. <span class="Apple-style-span">单目运算符的优先级仅次于前者,在所有真正意义上的运算符中,他们的优先级最高。类型转换也是单目运算符。他们自右至左结合。接下来是双目运算符,其中,算术运算符优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。</span>

    3. <span class="Apple-style-span">我们需要记住的最重要两点是:</span>

      1.  <span class="Apple-style-span">任何一个逻辑运算符的优先级低于任何一个关系运算符。</span>
      
      1. <span class="Apple-style-span">移位运算符优先级比算术运算符低,但比关系运算符高。</span>
  8. <span class="Apple-style-span">任何两个逻辑运算符都具有不同的优先级,所有按位运算符优先级要比顺序运算符高,每个“与”运算符要比相应的“或”高,二按位异或介于按位与和按位或之间。</span>

  9. <span class="Apple-style-span">注意不要在if或while语句后面写一个分号,如果要写,请用大括号括起来。实际上,这也是我们提倡的一种编程风格。</span>

  10. <span class="Apple-style-span">C语言要求,在函数调用时即使函数不带参数,也应包括参数列表。因此,如果f是一个函数,f()时一个函数调用语句,而f是一个什么也不做的语句,更精确的说,它计算函数f的地址,却并不调用该函数。</span>

  11. <span class="Apple-style-span">悬挂else问题的解决方法:else总是与同一括号内最近的未匹配的if结合。</span>

  12. <span class="Apple-style-span">C语言中只有一维数组,而且数组大小在编译时就作为一个常数确定下来。然而,数组元素可以是任何类型的对象,当然也可以是另外一个数组。</span>

  13. <span class="Apple-style-span">对于一个数组,我们只能做两件事:确定数组大小,以及获得指向该数组下标为0元素的指针。其他对数组的一切操作,都是通过指针来进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算。</span>

  14. <span class="Apple-style-span">对于int ar[12][13],说明ar数组拥有12个数组类型的元素,其中每个元素都是拥有13个整型元素的数组。(而不是反过来的),因此,sizeof(ar)的值是1213sizeof(int)。</span>

  15. <span class="Apple-style-span">对于除sizeof之外的其他场合,ar总被解释为指向数组起始元素的指针。</span>

  16. <span class="Apple-style-span">对于int ar[12];,&ar是一个指向数组的指针,而a是指向数组首元素的指针。</span>

  17. <span class="Apple-style-span">eg:int p;,对指针的声明解释方式应该是p是一个整型值,所以p就是一个指向整型元素的指针。</span>

  18. <span class="Apple-style-span">由于栈顶在内存中处于低地址空间,栈底处于高地址空间,</span><span class="Apple-style-span">故数组在入栈时是从后往前的(也就是由下标大的值依次往下标小的值压栈)</span>

  19. <span class="Apple-style-span">C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符('\0')的内存区域的地址。</span>

  20. <span class="Apple-style-span">malloc函数可能无法开辟足够的空间,因此可能返回空指针。然而,即使开辟成功,也应在使用完毕后及时释放该空间。</span>

  21. <span class="Apple-style-span">在字符串拷贝时,新开辟的空间大小往往是strlen(s)+1。</span>

  22. <span class="Apple-style-span">C语言无法将数组作为参数传递给一个函数。如果我们使用数组名作为参数,它会被转换为指向数组第一个元素的指针。但其他情况下不会这样转换,比如:</span><span class="Apple-style-span">extern</span><span class="Apple-style-span">char</span><span class="Apple-style-span"> hello</span><span class="Apple-style-span">[];和</span><span class="Apple-style-span">extern</span><span class="Apple-style-span">char</span><span class="Apple-style-span">*</span><span class="Apple-style-span">hello</span><span class="Apple-style-span">;</span>

  23. <span class="Apple-style-span">C语言中将一个常数转换为一个指针,得到的结果取决于具体的C编译器。但有一个例外,就是0。编译器保证由0转换而来的指针不等于任何有效的指针。处于代码文档化的考虑,常数0经常用一个符号来代替:#define NULL 0。</span>

  24. <span class="Apple-style-span">将0转化为指针使用时,该指针绝对不能解除引用(dereference)。</span>

  25. <span class="Apple-style-span">避免“栏杆错误”的原则:</span>

  26. <span class="Apple-style-span">首先考虑最简单情况下的特例,然后将得到的结果外推。</span>

  27. <span class="Apple-style-span">仔细计算边界,绝不掉以轻心。</span>

  28. <span class="Apple-style-span">或者将栏杆的边界设为左闭右开区间(直接相减,结果不用加一,即不对称边界)。</span>

  29. <span class="Apple-style-span">求值顺序:和运算符的优先级不是一回事。典型的运算符有&&(若左边为假,则不会对右边求值),||(和&&一样,先对左侧求值,只在需要时才对右侧操作数求值),?:(eg:a:b?c;先对a求值,再根据a的值决定对b还是c进行求值),,(逗号,先对左侧求值,然后该值被丢弃,再对右侧操作数求值)。实际上,C语言只有这四种运算符存在规定的求值顺序,其他运算符对其操作数的求值顺序是未定义的。</span>

  30. <span class="Apple-style-span">承上,要说明的是,分隔函数参数的逗号并非是逗号运算符。例如,函数f需要两个参数,则f(x,y);的求值顺序是未定义的;而函数g只需要一个参数,则g(x,y)先对x求值,然后将其丢弃,再对y求值。特别地,复制运算符并不保证任何求值顺序。</span>

  31. <span class="Apple-style-span">关于整数运算:无符号数没有“溢出”之说,因为无符号数是以2的n次方为模(n是结果中的二进制位数)。如果一个有符号数和一个无符号数进行运算,有符号数会被转换为无符号数,所以也不会溢出。</span>

  32. <span class="Apple-style-span">main函数如果不写返回值,默认为int。一个返回值为整型的函数如果返回失败,实际上隐式地返回了某个“垃圾”整数,只要该值不被用到,就无关紧要。但有些情况下对于main的返回值却并非如此,大多数C语言实现都通过main返回值来告知操作系统该操作是成功还是失败。典型的处理方案是,返回值为0代表执行成功,非0代表失败。如果该程序被别的程序调用,且main没有返回值,那么有可能看上去执行失败,得到令人惊讶的结果。</span>

  33. <span class="Apple-style-span">许多系统中连接器是独立于C语言实现的,且与C编译器分离,它不可能了解C语言的诸多细节。但它能够理解机器语言和内存布局。C编译器有责任以适当的方式通知连接器,确保未指定初始值的外部变量初始化为0。</span>

  34. <span class="Apple-style-span">static定义的变量(或函数)的作用域值局限于本文件内,其他文件是不可见的。</span>

  35. <span class="Apple-style-span">如果一个函数在被定义或声明之前被调用,那么他的返回值类型默认为整型,但这往往会得出错误的结果。C语言的规则是,如果一个未声明的标示符后面跟了一个开括号,那么它被视为一个返回整型的函数。</span>

  36. <span class="Apple-style-span">如果一个函数的参数中没有float,short或char类型的参数,在声明中可以省略其参数类型的说明。</span>

  37. <span class="Apple-style-span">如果在一个源文件中定义一个变量,在另一个源文件中用external声明它,则他们的类型必须相同(这是程序员的责任)。尤其注意,不要定义为char name[];而声明为char *name;</span>

  38. <span class="Apple-style-span">对于char c; (c=getchar())!=EOF;c被声明为char类型,而不是int类型,这意味着,c无法容纳所有可能的字符,特别是,可能无法容纳EOF。一种可能是,某些合法的字符被“截断”后使得c的值与EOF相同;另一种可能是,c根本无法取得EOF的值。对于前一种情况,文件将在复制的中途终止;对于后一种情况,程序将陷入死循环。但实际上,可能还有第三种情况,就是编译器直接在while中比较getchar()的返回值和EOF,而不是将c拿来比较,这样的话,程序看起来“似乎”能够正确运行。</span>

  39. <span class="Apple-style-span">对文件的读写:为了保持与过去不能同时进行读写操作的程序向下兼容性,一个输入操作后不能直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出,必须在其中插入fseek()函数的调用。</span>

  40. <span class="Apple-style-span">所有的C语言实现中都包括有signal()库函数,作为捕获异步事件的一种方式。(要使用它,需引入signal.h头文件)</span>

  41. <span class="Apple-style-span">因为函数调用有一定的开销,所以将一些小的函数定义为宏,可以提高运行时效率。但定义宏时,要确保其中的参数没有副作用,并且为每一个参数加上括号。</span>

  42. <span class="Apple-style-span">不能忽视宏中的空格;宏并不是语句;宏并不是函数;宏并不是类型定义;</span>

  43. <span class="Apple-style-span">对于标示符的规定,ANSIC所能保证的只是,C语言必须能够区别出前6个字符不同的外部名称,并不区分字母的大小写。因此,若两个函数的名称为print_fields和print_float,或者State和STATE,这样的命名就不恰当。</span>

  44. <span class="Apple-style-span">C语言的定义中对3种不同类型整数的相对长度做了一些规定(short,int,long):</span>

  45. <span class="Apple-style-span">3种类型的整数其长度是非递减的;</span>

  46. <span class="Apple-style-span">一个普通整数(int)足够大以容纳任何数组下标;</span>

  47. <span class="Apple-style-span">字符长度由硬件特性决定。</span>

  48. <span class="Apple-style-span">如果c是一个字符变量,想用(insigned)c将其转化为无符号整数,这时会失败的。因为在字符c转化为无符号整数时,c首先会被转化为int型整数,而此时可能得到非预期的结果。正确的方式是(insigned char)c,因为unsigned char类型的字符在转化为无符号整数是无需转化为int型整数,而是直接进行转化。</span>

  49. <span class="Apple-style-span">对于移位运算符:向右移位时,如果被移位的对象是无符号数,那么空出的为将被0填充;若是有符号数,则既可用0填充(逻辑移位),也可用符号位填充(算数移位)。</span>

  50. <span class="Apple-style-span">移位计数允许的取值范围是0~n(n是该变量的二进制位数),即大于等于0,而小于n。因此,不可能在单次操作中将某个数的所有位都移出。为什么要有这个限制呢?原因是只要加上了这个限制,我们就能在硬件上高效的实现移位运算。</span>

  51. <span class="Apple-style-span">移位运算符的效率要比除法高效的多。但即使C实现将符号位复制到空出的位中,有符号整数的向右移位也不等同于除以2的某次幂。证明:(-1)>>1,这个操作结果一般不可能为0,但(-1)/2在大多数C实现上结果都是0。</span>

  52. <span class="Apple-style-span">内存位置0:NULL指针不指向任何对象。因此,除非是用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的。</span>

  53. <span class="Apple-style-span">除法运算时发生的截断:假定我们让q=a/b;r=a%b;(假定b大于0)。我们希望a,b,q,r之间维持的关系是:</span>

  54. <span class="Apple-style-span">最重要的一点:我们希望q*b+r==a,因为这是余数的定义。</span>

  55. <span class="Apple-style-span">如果我们改变a的符号,我们希望改变q的符号,但不会改变其绝对值。</span>

  56. <span class="Apple-style-span">当b>0时,我们希望保证r>=0且r<b。 </span><span class="Apple-style-span">但是很不幸,以上三条无法同时成立(可以自行验证)。因此C语言(或其他语言)在实现除法时,必须放弃其中的至少一条,大多数程序设计语言选择放弃第3条,而改为余数与被除数的正负号相同。</span><span class="Apple-style-span">然而,C语言的定义只保证了性质1,以及当a>=0且b>0时,保证|r|<|b|以及r>=0。</span>

  57. <span class="Apple-style-span">rand函数有两个版本,分别是VAX-11(返回值范围为02<sup>31</sup>-1)和AT&amp;T(返回值范围为02<sup>15</sup>-1)。如果我们用到了rand函数,就必须根据特定的C语言实现做出“剪裁”。ANSCI标准中定义了一个常数RAND_MAX,它的值等于随机数的最大取值。</span>

  58. <span class="Apple-style-span">在一个负数前加上-转化为整数有可能溢出。</span>

  59. <span class="Apple-style-span">建议</span>

  60. <span class="Apple-style-span">在编写程序时,考查最简单的特例。比如当部分输入数据为空或只有一个元素时。</span>

  61. <span class="Apple-style-span">使用不对称边界。例如数组的下标。</span>

  62. <span class="Apple-style-span">进行预防性编程。 <完></span>