首页 > 代码库 > 《C陷阱与缺陷》读书笔记

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

C陷阱与缺陷》读书笔记

 

1.编译器中的词法分析器负责将程序分解为一个个符号。C语言中,符号之间的空白 (包括Space ,Tab , Enter) 都将被忽略,但一个符号的中间不能有空白,否则可能被解释成为另一个或几个符号。

 

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

 

3.如果一个整型常量的第一个字符是数字0,那么该常量将被视为8进制数。10和010含义不同,有时为了格式对齐的需要,可能无意中将10进制数写成了8进制数。如:

046,“ Maybe you are right ! ”

047,“ Maybe you are wrong ! ”

123,“ I do not know! ”

 

4.用单引号引起的一个字符实际上代表一个整数,整数的值等于该字符在编译器采用的字符集的序列值,而用双引号引起的字符串,实际代表的却是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的2进制值为0的字符 ’ /0 ’ 初始化。

 

5.<1>逗号运算符的优先级最低;

<2>任何一个逻辑运算符的优先级低于任何一个关系运算符;

<3>移位运算符的优先级比算术运算符要低,但比关系运算符高;

<4>单目运算符优先级仅次于( ),[], à 和 . 这些最高级运算符,单目运算符和赋值运算符的结合性自右至左,最高级运算符自左至右。

 

6.若f是一个函数,则f ( ) ; 是一个函数调用语句,而f ; 仅仅计算函数f的地址,并不调用该函数。

 

7.else始终与同一对括号内最近的未匹配的if结合。

 

8.int calendar[12][31]语句声明calendar是一个拥有12个元素的数组,每个元素又是一个含有31个整形元素一维数组。Sizeof (calendar) 的值是12×31×sizof (int) 的积。对于calendar[n],是calendar数组的第n+1个元素,是calendar数组中12个有着31个整形元素的数组之一。sizof (calendar [4 ]) 的结果是31与 sizof (int) 的乘积。int *p ;  p = calendar[4] ; 使指针p指向了数组calendar[4] 中下标为0的元素。int i ; i = calendar[4][7] ; 还可以写成 i = *(calendar[4]+7) ; 进一步可以写成i = *(*(calendar+4)+7) ; 还要注意:calendar是个二维数组,它会被转换为一个指向数组的指针,不能将其赋给一个指向整形变量的指针,而应赋给类型兼容的数组指针。

 

9.库函数strlen( ) 技术参数中字符串所包含的字符串数目是不包含作为结束标志的空字符串的。即如果strlen ( s ) 的值是n,那么字符串实际需要n+1个字符的空间。

 

10.malloc ( ) 应与free ( ) 配套使用,若malloc无法提供请求时会通过返回一个空指针来作为“内存分配失败”的信号,如果有char * r ; r = malloc (n) ; 的语句,则应该有if (! r ) { 一些处理语句}。

 

11.C语言中会自动地将作为参数的数组声明转换为相应的指针声明(即指向数组第一个元素的指针),但仅限于这种情况下。

 

12.若有char * p , * q ;  p = “ abc ” ; p的值是一个指向由’ a ’,’ b ’,’ c ’和’ /0 ’ 4个字符组成的数组的首元素的指针,如果执行 p = q ; p和q是两个指向内存中同一地址的指针,此语句并没有同时赋值内存中的字符。注意:复制指针并不同时复制指针所指向的数据。ANSI C禁止对字符串常量(string literal)作修改,此行为的结果是未定义的。

 

13.不对称边界:用第一个入界点和第一个出界点来表示一个数值范围,取值范围的大小就是上下界之差。此种做法的优点是:如果取值范围为空,那么上界等于下界;上界永远不可能小于下界。像for (i = 0 ; i < 10 ; i++) 这种写法是十分提倡的。

 

14.数组中实际上不存在的“溢界”元素的地址在数组所占内存之后,这个地址可以用于进行赋值和比较,ANSI C明确允许这用法,但不能解引用,或进行下标计算。

 

15.C语言中只有4个运算符( &&  ,  | |  ,  ? : 和 , )存在规定的求值顺序。&&和| |首先对左侧操作数求值,只在需要时才对右侧操作数求值;对于a ? b : c,操作数a首先被求值,根据a的值再求操作数b或c的值。对于逗号运算符,先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。

 

16.C语言中其他所有运算符对其操作数求值的顺序都是未定义的,特别要注意:赋值运算符( = )并不保证任何求值顺序;还有,分隔函数参数的逗号并非逗号运算符,函数f ( x , y )中的求值顺序是未定义的,而在函数g (( x , y ))中,只有一个参数,即( x , y ),此参数的求值顺序是:先对x求值,然后x的值被“丢弃”,接着对y求值。

 

17.对数组赋值时不要使用类似的语句: y[ i ]= x [ i++ ]; 因为求值顺序是不确定的,y[ i ]的地址并不一定在i 的自增操作执行之前被求值,所以应避免此类方式;若使用同一计数器i,可以写成y[ i ]= x [ i ];  i ++ ;

 

18.按位运算符 & ,  |  ,  ^ 和 ~ 对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作;另一方面,&& 、| | 和 !对操作数的处理方式是将其视为要么是“真”(即“1”),要么是“假”(即“0”),它们只可能返回0或1。

 

19.main函数的返回值用来告知操作系统该函数的执行是成功还是失败,通常0代表成功。

 

20.如果语句int a ; 位置出现在所有的函数体之外,则为外部对象a的定义。此语句说明了a为一个外部整型变量,并为a分配了存储空间但没有初始化,不过编译器会保证它的初始值为0;int a = 7 ; 定义a的同时也为a明确制定了初始值。这个语句不仅为a分配内存,而且也说明了该内存中应该存储的值;extern int a ; 并不是对a的定义,这个语句仍然说明了a是一个外部变量,但是因为它包括了extern关键字,就显式说明a的存储空间是在程序的其他地方分配的。

 

21.每个外部变量都必须在程序某个地方进行定义如果一个程序中有extern int a ; 则在别的某个地方必须包括语句 int a ; 这两个语句既可以在同一个源文件中,也可以位于程序的不同源文件中。每个外部变量只能够定义一次。

 

22.为了预防可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,则应该声明该函数为static。

 

23.每个对象都应该声明在一个头文件里,需要用到该外部对象的所有模块都应该包括这个头文件,特别注意:定义该外部对象的模块也应该包含这个头文件。

 

24.宏只是对程序的文本起作用,就是说宏提供了一种对组成C程序的字符进行变换的方式,它并不作用于程序中的对象,不能忽视宏定义中的空格。

 

25.最好在宏定义中把每个参数都用括号括起来,整个结果表达式也应该用括号括起来,以防止当宏用于一个更大一些的表达式中可能出现的问题。

 

26.__FILE__ 和 __LINE__ 是内建于C语言预处理器中的宏,他们会扩展为所在文件的文件名和所处代码行的行号。

 

27.宏并不是类型定义。对于# difine  T1  struct  foo * 和 typedef  struct  foo * T2 ; T1和T2从概念上完全相同,都是指向结构foo的指针。但是,当我们试图用他们声明多个变量时,则会出问题:T1  a , b ; T2  a ,b ; 第一句被扩展为struct  foo *  a , b ;  a被定义为一个指向结构的指针,而b却被定义为一个结构(不是指针)。第二句则不同,它定义了a和b都是指向结构的指针。

 

28.C语言是区分大小写的 (Case Sensitive),ANSI C标准只保证C实现必须能够区别出前6个字符不同的外部名称,比如:两个函数的名称分别为print_fields与print_float,这样的命名方式就不恰当。

 

29.ANSI C标准要求long型整数的长度至少应该是32位,而short型和int型整数的长度至少应该是16位。

 

30.如果a是一个字符变量,使用(unsigned) a并不能得到与a等价的无符号整数。以为再将字符a转换为无符号整数时,a将首先转换为int性整数,而此时可能得到非预期的结果。正确的方式是使用语句(unsigned char) a,因为unsigned char类型的字符在转换为无符号整数时无需首先转换为int型整数,而是直接进行转换。

 

31.如果被移位的对象长度是n,那么移位计数必须大于或等于0,而严格小于n。所以不可能做到在单次操作中将某个数值中所有位都移出。Visual C++中,对于无符号数右移时左边填0,对于有符号数,原来是0则填0,原来是1则填1。

 

32.ANSI C中定义了一个常数RAND_MAX ,它的值等于随机数的最大取值,在Visual C++中,RAND_MAX的定义为# define  RAND_MAX  0x7fff 。

 

33.调用realloc函数时,需要把指向一块已分配内存的区域指针以及这块内存新的大小作为参数传入,就可以调整(扩大或缩小)这块内存区域为新的大小,这个过程中有可能涉及到内存的拷贝。

 

34.无论是构思程序的工作方式还是测试程序的工作情况,考察最简单的特例这一原则都适用。当部分输入数据为空或者只有一个元素时,很多程序都会执行失败。

 

35.printf函数的第一个参数是关于输出格式的说明,它是一个描述了输出格式的字符串。printf函数把格式说明字符串中的字符逐个复制到标准输出,直到格式字符串结束或遇到一个%,printf函数并不打印%字符,而是查看紧跟%在字符之后的若干字符,以获得有关如何转换其下一个字符的指示。

 

36.格式字符串中的每个格式项都有一个%符号打头,后面接一个称为格式码的字符 ,格式码指明了格式转换的类型。格式码与%之间可以夹一些可选的字符,每个格式项都是以格式码结束。

 

37.%d格式项打印一个相应的10进制整数;%u打印无符号10进制整数;%o (%O已经被ANSI C废止),%x,%X格式项用于打印8或16进制的整数;%X只是用大写字母A、B、C、D、E和F来表示。%s格式项用于打印字符串,与之对应的参数应该是一个字符指针,待输出的字符始于该指针所指向的地址,直到出现一个空字符(’/0’)才终止。

 

38.%g、%f和%e这3个格式项用于打印浮点值,%g在打印出对应的数值时,会去掉该数值尾缀的零,保留6为有效数字。%E和%G格式项与它们对应的%e和%g格式项在行为方式上基本相同,除了用大写的E代替了小写的e来表示指数形式。%%格式项用于打印出一个%字符。这个格式项的独特之处在于它不需要一个对应的参数。

 

39.预处理器的作用范围不能达到字符串的内部。

 

40.ANSI C标准的定义中新增了两个格式码:%p和%n。%p用于以某种形式打印一个指针(或该指针所指向的地址),%n用于指出已经打印的字符数,这个数被存储在对应参数(一个整形指针)所指向的整数中。

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