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

第一章《词法“陷阱”》
1,=和==,一个容易疏忽的地方
2,词法分析中的“贪心法”,诸如i++++++++j的理解,以前我总认为这是很无聊的问题,现在也是,呵呵,其实对这种问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。需要注意的是符号中间不能嵌有空白(空格符,制表符和换行符)如:==是单个符号,= =这是俩个符号。
3,单引号括起来的一个字符代表一个整数,而用双引号括起来的一个字符代表一个指针。


第二章《语法“陷阱”》
1,一个比较难的声明语句(*(void(*)())0)();构造这类表达式的规则:按照使用的方式来声明。第一步,假设fp是一个函数指针,那么调用fp所指向的函数方法为:(*fp)();ANSI C标准容许程序员将上式简写为fp(),但是一定要记住这种写法只是一种简写形式。
第二步,用一个恰当的表达式来代替fp。(void(*)())0把0强制转换为指向返回值为void的函数的指针类型。使用typedef能使表达式更加清晰:typedef void (*funcptr)();(*(funcptr)0)();
2,与问题1类似,一个同类型的例子是signal库函数的声明,signal函数接收俩个参数:一个是代表需要“被捕获”的特定signal的整数值;另一个是指向用户提供的函数的指针,该函数用于处理“捕获到”的特定signal,返回值类型为void。得到:void (*signal(int,void(*)(int)))(int);同样的,使用typedef可以简化上面的函数声明。typedef void(*HANDLER)(int);HANDLER signal(int,HANDLER);
3,C语言运算符优先级理解记忆,<1>优先级最高者其实并不是真正意义上的运算符,包括:数组下标,函数调用操作符各结构成员选则操作符。它们都是自左于右结合。<2>单目运算符的优先级仅次于前述运算符。单目运算符自右向左结合。<3>双目运算符中算数运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。我们需要记住的最重要的俩点是:任何一个逻辑运算符的优先级低于任何一个关系运算符。移位运算符的优先级比算术运算符要低,但是比关系运算符要高。三目运算符优先级最低。
4,注意分号,switch语句的break,else悬挂问题。


第三章《语义“陷阱”》
1,C语言中只有一维数组,而且数组的大小必须在编译期就 作为一个常数确定下来。然而,C语言中数组的元素可以是任何类型的对象,当然也可以是另一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
2,对于一个数组,我们只能够做俩件事:确定该数组的大小,以及获得指向该数组下标为0的元素指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。
3,int calendar[12][31];这个语句声明了calendar是一个数组,sizeof(calendar)的值是372(31*12)与sizeof(int)的乘积。如果calendar不是用于sizeof的操作数,那么calendar总是被转换成一个指向calendar数组的起始元素的指针。
4,指针赋值也需要俩个指针同类型。
5,在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第一个元素的指针。printf(“%s/n”,hello)与printf("%s/n",&hello[0])作用完全等效。
6,除了一个重要的例外情况,在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。出于代码文档化的考虑,常数0这个值经常用一个符号来代替:#define NULL 0 需要记住,这个指针绝对不能解引用。
7,不对称规则,(第一个出界点的值)-(第一个入界点的值)即为元素个数。如:若以0为起点,7为终点。那么0为第一个入界点,1,2,3,4,5,6,7为元素,8为第一个出界点。所以元素个数为8个。数组声明可以用 int a[8],for语句可以用for(i=0;i<8;i++).
8,注意&&,||,条件语句可能造成的求值短路。注意赋值语句不保证求值顺序,y = x[i++];求值顺序不确定。


第四章《连接》
1,C语言中的一个重要思想就是分别编译(Separate Compilation),即若干个源程序可以再不同的时候单独进行编译,然后在恰当的时候整合到一起。
2,连接器一般是与C编译器分离的,尽管连接器并不理解C语言,然而它却能够理解机器语言和内存布局。编译器的责任是把C源程序“翻译”成对连接器有意义的形式,这样连接器就能够“读懂”C源程序了。
3,典型的连接器把由编译器或汇编器生成的若干个模块,整合成一个被称为载入模块或可执行文件的实体,该实体能够被操作系统直接执行。其中,某些模块是直接作为输入提供给连接器的;而另外一些目标模块则是根据连接过程需要,从包括有类似printf函数的库文件中取得的。
4,连接器通常把目标模块看成是由一组外部对象组成的。每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别。因此程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。由于经过了“名称修饰”,所以它们不会与其他源程序文件中的同名函数或同名变量发生命名冲突。
5,大多数连接器都禁止同一个载入模块中的俩个不同外部对象拥有相同的名称。然而,在多个目标模块整合成一个载入模块时,这些目标模块可能就包含了同名的外部对象。连接器的一个重要工作就是处理这类命名冲突。
6,static int a;a的作用域限制在一个源文件内,对于其他源文件,a是不可见的。函数也可以用类似方法。


第五章《库函数》
1,C语言中没有定义输入/输出语句,任何一个有用的C程序(起码必须接受零个或多个输入,生成一个或多个输出)都必须调用库函数来完成最基本的输入/输出操作。
2,EOF是一个整型数,一般为-1,所以需要用int类型接受它。int a = getchar();
3,文件不能交错进行读写操作,如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。
4,程序输出有俩种方式:一种是即时处理方式,另一种是先暂存起来,然后再大块写入的方式,前者往往造成较高的系统负担。setbuf(stdout,buf);语句将通知输入/输出库,所有写入到stdout的输出和都应该使用buf作为输出缓冲区,直到buf缓冲区被填满或程序员直接调用fflush。

第六章《预处理器》
1,在严格意义上的编译过程开始之前,C语言预处理器首先对程序代码做了必要的转换处理。因此,我们运行的程序实际上并不是我们所写的程序。预处理器使得编程者可以简化某些工作。如:将数字定义为一个显示常量,#define MAX 500 这样修改MAX值比较容易。另外,大多数C语言实现在函数调用时都会带来重大的系统开销。因此,我们也许希望有这样一种程序块,它看上去像一个函数,但却没有函数调用的开销。
2,宏只对程序文本起作用,宏提供一种对组成C程序的字符进行变化的方式,而并不作用于程序中的对象。最好在宏定义中把每个参数都用括号括起来,防止使用时出现问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值