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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33897800/article/details/83743513

这本书很薄,仅有150来页,正文大概120页,最后附上了课后答案及建议。上周花了三天断断续续看完,先做一个总结。

第一章:词法陷阱

  1. 词法分析中的贪心算法:每个符号应该包括尽可能多的字符。因此,注释的嵌套是不允许的。
  2. 符号的中间不能嵌有空白(空格符、制表符和换行符)。例如,==是单个符号,= =则是两个符号。例如,
    a---b ;
    a -- - b ;
    a - -- b ;

    前两项含义相同,而不同于第三项。同样的,如果/是为判断下一个符号而读入的第一个字符,而/之后紧接着*,那么无论上下文如何,这两个字符都将被当做/*,表示一段注释的开始。例如

    y= x/*p;    /* p指向除数 */

    上述语句本意似乎是用x除以p所指向的值,再把所得的商赋给y。而实际上,/*被编译器理解为一段注释的开始,编译器将不断的读入字符,直到*/出现为止。上面的语句应当重写为下面的格式,得到的实际效果才是语句注释所表达的意思。

    y = x / *p;          /* p指向除数*/
    
    y= x/(*p);           /* p指向除数*/

                                                              

  3. 用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制为零的字符‘0’初始化。也就是说

    printf("hello\n");
    与
    char hello[]={'H','E','L','L','O','\n',0};
    printf(hello);
    是等效的

第二章 词法陷阱

  1. 假定fp是一个指向返回值为void类型的函数的指针,那么fp的声明如下:
    void (*fp)()

    调用fp所指向函数的方法如下:

    (*fp)();

    在表达式(*fp)()中,*fp两侧的括号非常重要,因为函数运算符()的优先级高于单目运算符*。

  2. 若知道了如何声明一个变量,那么就可以对一个常数进行类型转换,将其转换为该变量的类型:只需要在变量声明中将变量名去掉即可。因此,将常数0转换为“指向返回值为void类型的函数的指针”类型,可以这样写

    (void (*)()) 0

    因此我们可以用(void (*)()) 0来代替fp,从而得到

    *( (void (*)()) 0 ) ();

    末尾的分号使得表达式成为一个语句。

    扫描二维码关注公众号,回复: 3995765 查看本文章
  3. 运算符的优先级中,最重要的两点:a,总则:0目(数组下标,函数调用,结构成员选择)  > 单目 > 双目(算术,移位,关系依次递减) > 三目 > 赋值;b,任何一个逻辑运算符的优先级低于任何一个关系运算符;移位运算符的优先级比算术运算符低,但是比关系运算符高。

第三章 语义陷阱

  1. 数组和指针:在讲数组作为函数的参数传递的时候,数组会自动被转化为指针的形式。
  2. 在C语言中将一个整数转换为一个指针,最后得到的结果取决于具体的C编译器的实现。
  3. 声明数组:
    int a[3];

    上式声明了a是一个拥有3个整形元素的数组。类似的,

    struct {
        int p[4];
        double x;
    }b[17]

    上式声明了b是一个拥有17个元素的数组,其中每个元素都是一个结构,该结构中包括了一个拥有四个整形元素的数组(命名为p)和一个双精度类型的变量(命名为x)。现在考虑下面的例子,

    int calendar[12][31]

    这个语句声明了calendar是一个数组,该数组拥有12个十足类型的元素,其中每个元素都是一个拥有31个整形元素的数组因此,sizeof(calendar)的值是372(31*12)与sizeof(int)的乘积。

  4. 如果一个指针ip指向的是数组中的一个元素,那么ip+1就指向该数组种下一个元素的地址。对于除1之外的其他整数类型,以此类推。例如:

    *(calendar+4)= calendar[4]
  5. 如果两个指针只想的是同一个数组中的元素,那么两个指针相减是有意义的,而相加没有意义。对于两个指向不同数组的指针,相加和相减都没有意义。

  6. 引用越界元素非法,但引用越界元素指向的地址合法。(P50)

  7. 整数溢出:首先要明确一点,无符号算数运算中,没有“溢出”这一说(另外,一个无符号数和一个有符号数进行运算,有符号数会转换成无符号数)。溢出只针对两个都是有符号的算术运算。对于两个均为有符号数的算术运算,若a和b为非负整数,检查a+b是否会溢出? a+b < 0 是不能正确执行的。如某些机器上,数据运算的结果为正/负/零/溢出四种,则a+b时,内部寄存器会出现溢出而不是负,则if检查失败。正确方式是将a和b都转换成无符号整数。if((unsigned)a + (unsigned) b > INT_MAX),其中INT_MAX在<limits.h>中所定义。或者 if(a > INT_MAX -b)。

第四章 连接

  1. 链接器的作用:输入一组目标模块和库文件,输出一个载入模块。其主要作用是解决命名冲突。
  2. static修饰符(静态全局变量)作用有二:其一是声明变量或者函数是静态的;其二是被static修饰的变量或者函数只能在该源文件内引用。换句话说,如果一个函数仅仅被同一个源文件的其他函数调用,就应该声明该函数为static。这样可以避免与标准库文件中的外部对象名称发生冲突。
  3. 静态全局变量static和其他全局变量的存储地点并没有区别,都是在data段(已初始化)或.bss段(未初始化)内。但是它只在定义它的源文件内有效,其他源文件无法访问。
  4. 如果一个未标明的标识符后面跟一个开括号,那么它将被视为一个返回整型的函数。
  5. 如果一个函数在被定义或者声明之前被调用,那么它的返回类型就默认为整形。
  6. 每个外部对象只在一个地方声明。这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。特别需要指出的是定义该外部对象的模块也应该包括这个头文件。

第五章 库函数

  1. signal信号处理函数需要尽可能的简单。

第六章 预处理器

  1. 不能忽视宏定义中的空格。
  2. 宏并不是函数。
  3. 宏并不是语句。
  4. 宏并不是类型定义。类型定义请使用typedef。

第七章 可移植性缺陷

  1. 一个常见的错误是:如果c是一个字符变量,使用(unsigned)c就可以得到与c等价的无符号整数。这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果。正确的做法是:使用语句(unsigned char)c,因为一个unsigned char类型的字符在转换为无符号数时无需首先转换为int整形,而是直接进行转换。
  2. 在移位运算中,如果被移位的对象是n位,那么移位计数必须大于或等于0,而严格小于n。
  3. 移位运算的速度远大于除法运算。
  4. null指针并不指向任何向量。
  5. 调用malloc(n)将返回一个指针,指向一块新分配的可以容纳n个字符的内存,编程者可以使用这块内存。把malloc函数返回的指针作为参数传入给free函数,就释放了这块儿内存,这样就可以重新利用了。

猜你喜欢

转载自blog.csdn.net/qq_33897800/article/details/83743513