《C陷阱与缺陷》阅读笔记1

来源:互联网 发布:小知运营助手下载 编辑:程序博客网 时间:2024/05/16 19:50

第一章  词法“陷阱”

1.当需要对变量进行赋值并检查该变量的新值是否为0时,为了避免编译器的警告

    需要这样写:(加括号是考虑运算符的优先级)

if((x = y) != 0){    foo();}
   而不是
if(x = y){   foo();}
2."贪心法“来分解字符

    ”贪心法“:如果输入流截止至某个字符之前都已经被分解成一个个字符,那么下一个符号将包括从该字符之后可能组成一个符号的最长字符串(从左到右)

     例如:

a---b
分解字符应为
(a--)-b
    又例如:
a+++++b
分解字符应为:
a++ ++  +b

由于单目运算符++的结合型性是从右到做,因此调试时出现error :"++"need left value,左值问题

第二章 语法“陷阱”

1.函数指针

float   (*h)();  

表示h是一个指向返回值为浮点类型的指针;(float  (*)() )表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。

例如:

( void (*)() )0
表示对0进行强制类型装换,转换为 返回值为空的函数的指针的类型

这样书中给出的一个表达式:

( *( void (*)() )fp )()
这个表达式的含义就是:将fp转换了一个”指向返回值为浮点类型的函数的指针“的类型的函数指针;

当然,利用typedef能够使上述表达式更加清晰:

typedef void (*pfunc) ();(*(pfunc)fp)()

还有一点需要注意,函数运算符()的优先级高于单目运算符*,*fp()==*(fp())==*((*fp)()).

下面看下面这一表达式的含义:

void(*signal (int ,void (*)(int) ) (int)
定义一个signal函数,该函数的参数有两个,一个为int 型,一个为”指向返回值为空,参数为int型的指针”类型;该函数的返回值为函数指针类型,该函数指针指向返回值为空,参数为int型的函数。

当然,利用typedef也能够使上面的表达式更加清晰易懂

typedef void(*HANDLER)(int);HANDLER signal(int,HANDLER)

函数指针最现实的应用场景应该是回调(CallBack)函数

2.运算符的优先级

遇到运算符的优先级拿不准的时候,就应该加括号,但过多的括号也是表达式的可读性变差,因此应该尽可能的记住常用的运算符的优先级。

下面是C语言的运算符优先级(由上到下,优先级依次递减)

OperatorsAssociativity() [] -> . left to right! ~ ++ -- + - * (type) sizeof <strong>right to left</strong>* / % left to right+ - left to right<<  >> left to right< <= > >= left to right== != left to right& left to right^ left to right| left to right&& left to right|| left to right?: <strong>right to left</strong>= += -= *= /= %= &= ^= |= <<= >>=<strong> right to left</strong>, left to right

关于运算符需要记住最重要的5点:

1.单目运算符高于双目运算符,单目运算符的结合性为从右到做,比如*p++等价于*(p++);

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

3.移位运算符的优先级比算术运算符优先级低,但比关系运算符要高。

4.赋值运算符的结合性为从右到左,赋值运算符的优先级低于条件运算符。

5.条件运算符优先级高于赋值、逗号运算符,低于其他运算符


第三章 语义陷阱

1.二维数组的构造和传参

2.原码和补码

 数字是以补码的形式存放在机器中,正数数的原码和补码是相同的,负数的补码是原码的符号位不变,其余为按位取反,最后加1得来的。例如,-11的原码为100...001011,对应的补码为111...110101,即-11在机器中的存储形式为111...110101。

3.整数溢出

如果算术运算符的一个操作数是有符号整数,而另一个操作数为无符号整数,那么有符号整数会转换为无符号整数,“溢出”就不可能发生;但是,当两个操作数都是有符号整数时,“溢出”就有可能发生,而且“溢出”的结果都是未定义的。

第四章 连接

1.目标代码文件(.obj)、可执行文件(.exe)和库

   C文件经过编译生成.obj格式的目标代码,目标代码中仅包含用户自己编写的那一部分程序转换成机器语言后的内容,但还不是一个完整的程序;连接器的作用是将目标代码系统的标准启动代码库代码结合在一起,并将它们放在单个文件,即可执行文件(.exe)。

   目标文件和可执行文件都是由机器语言指令组成的。

   static可以限制函数或者变量的文件作用域。

2.声明与定义

   extern 修饰的变量是对外部变量的引用。

3.static修饰符与命名冲突

   如果一个程序对同一个外部变量的定义不止一次,例如 int a=7;定义在一个源文件中,而int a=9;定义在另一个源文件中。由于严格的规则是 ,每个外部变量只能定义一次,因此上述程序不能被系统接收,而通过使用static修饰符可以避免这种冲突。

  例如,我们在一个文件中定义 static int a=7;那么该变量的作用域就被限制在该文件内,对其他源文件,a是不可见的。

  对于一下声明语句:

staitc int a ;
int a;
这两个语句的含义相同,只是变量的作用域不同。

static 修饰符不仅适用于变量,也适用于函数。如果函数f()需要调用另一个函数g(),而且只有f()需要调用函数g(),那么可以将函数f()和g()放到同一个源文件中,并且声明函数g()为static;

static int g(){   //其他语句    ...}int f(){   //其他语句   ...   g()}
如果多个源文件中定义同名的函数,只要多有的函数g()都被定义成static就可以把函数作用域限制在定义该函数的文件中。

因此为了避免冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,就应当声明该函数为static。





0 0