《C陷阱与缺陷》 Andrew Koenig 读书笔记
附录来自网络
2.1 理解函数的声明
2.1.1 如何声明一个给定类型的变量
任何c变量的声明都由两部分组成:
类型
以及一组类似表达式的声明符
float f,g; //f和g的类型为float
float ((f));//((f))的类型为浮点型
float ff(); //表达式ff()求值结果为float
float *pf; //*pf是一个浮点数,也就是pf指向一个浮点数的指针
float *g(),(*h)();
表示*g( )与(*h)( )是浮点表达式。
因为( )结合优于*,*g( )也就是*(g( ));
g是一个函数,返回值为float的指针
h是一个函数指针,h指向的函数的返回值为浮点类型
2.1.2 如何得到一个类型的转换符
一旦知道了如何声明一个给定类型的变量,那么该类型的类型转换符就是:
把声明中的变量名和声明末尾的分号去掉,再将剩下的部分用一个括号整个“封装”起来即可
float (*h)();
表示h是一个返回值为float的函数指针,因此,
(float (*)())
表示一个“指向返回值为浮点类型的函数的指针”的类型转换符
2.1.3 分析(*(void(*)())0)();
上面的程序是调用首地址为0位置的例程。
- 第一步 前导知识
假定fp是一个函数的指针,如何调用fp所指的函数?(*fp)()
fp指向一个函数,*fp就是该指针指向的函数,(*fp)( )就是函数的调用了
注意:在ANSI C 标准中可以简写为fp( )
,但这只是一种简写形式
注意
*fp
两侧的括号,非常重要,因为()
的优先级高于*
,所以在没有括号的情况下就变成了*(fp( ))
,由于fp()
是(*fp)()
的简写,故也变为了*((*fp)())
- 第二步 分析
(*0)();
上面的式子是错误的,因为*操作的对象必须是一个指针,而0
是一个常量
那么就需要将常数0类型转换为函数的指针
指针实际上是一个4个字节用来保存地址的数,如果将0转化为函数的指针,由于指针的值为0,故转换后会变成指向0地址的函数的指针
由2.1.1可以知道,转化为返回值为void类型的函数的指针加上
(void(*)( ))
即可
(* (void(*)()) 0 )()
- 进一步扩展
利用typede可以更加简化函数的声明
例如:void (*signal(int,void(*)(int)))(int)
可以简化为:
typedef void (*HANDLER)(int);
HANDLER signal (int,HANDLER);
关于上面typedef的解读,见下面的附录
2.2 运算符的优先级问题
记住优先级很好,因为太多的括号会影响阅读,如果记不住,还是加括号吧。
记住三点:
- 任何一个逻辑运算符的优先级低于任何一个关系运算符(加减乘除...)
- 位移运算符的优先级比算数运算符要低,但比关系运算符(与或非...)要高。
- 三目运算符优先级最低
2.3 注意语句结束的;
据说90%的错误都是因为;
2.4 switch 语句
注意其中的break;
语句。
漏掉后会造成不可预计的错误,当然有意的漏写,可以实现特别的控制结构。
2.5 函数调用
不带参数也需要加参数列表;
f();
而不是
f;
2.6 "悬挂"else引发的问题
这段代码中的else实际作用是在第二个if之后。
建议:
使用{ }
哪怕只有一句执行语句。
附录
关于typedef
在signal函数中有这样的定义:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
此处由于加了typedef自定义了一个新类型sighandler_t,所以第二行的函数原型看起来顺眼多了,形式跟int func(char c, int i)无异,但是如果看不懂typedef语句,这两句话仍然是噩梦。
要理解typedef,只要记住一句话就差不多了,那就是:
typedef在语句中所起的作用只不过是把语句原先定义变量的功能变成了定义类型的功能而已
。我们只消看几个例子立即明白。
例如语句 typedef int *apple; 理解它的正确步骤是这样的:先别看typedef,就剩下int *apple; 这个语句再简单不过,就是声明了一个指向整型变量的指针apple (注意:定义只是一种特殊的声明),加上typedef之后就解释成声明了一种指向整型变量指针的类型apple 。
现在,回过来看上面的这个函数原型 typedef void (*sighandler_t)(int),盖住 typedef不看 ,再简单不过,sighandler_t就是一个函数指针,指向的函数接受一个整型参数并返回一个无类型指针 。加上typedef之后sighandler_t就是一种新的类型,就可以像int一样地去用它,不同的是它声明是一种函数指针,这种指针指向的函数接受一个整型参数并返回一个无类型指针 。怎么样?简单吧。
例子:
再来做一个更酷的练习,请看:typedef char *(* c[10])(int **p);
去 掉typedef就变成char *(* c[10])(int **p)
,先不管这个语句有多难看,它一定是声明了一个拥有10个元素的数组c对不对?okay没什么了不起的,只不过这个数组c的元素有点特别,它们都 是函数指针,并且它们所指向的这些函数统统都接受一个二级指针然后返回一直指向字符型的指针。加上typedef之后,c就不是一个数组了,而是一种类型 了,什么类型现在你能说出来了吧。 _
说到typedef就不能不把它跟宏替换比较,typedef相对于宏替换是一种彻底的“替换”,#define之所以被称为宏替换,是因为它就是简单地照搬替换字符串。来看个例子:
例1 typedef int x[10];
例2 #define x int[10]
例1定义了类型x,此时我们就可以用它来定义别的变量了,比如x y; 此时y是一个拥有10个整型变量的数组,效果与语句int y[10]无异。typedef带给我们的是一种彻底的封装
。
例2用了宏定义的方式,将来在该宏的作用域范围内的任何地方遇到的x都将被简单地替换成int [10]。
(注意到,宏替换结尾是不带分号的,不同于typedef语句
)
对于typedef和宏可以有以下总结:
1、宏定义可以扩展,彻底封装的typedef不可以。
//以下代码完全没问题:
#define apple int;
unsigned apple i;
//以下代码则完全行不通:
typedef int apple;
unsigned apple i;
2、在连续声明几个变量的时候typedef可以完全保证变量是同一种类型,而宏替换无法保证。
#define apple int *;
apple a, b; //a和b类型完全不同,a是指向整型变量的指针,b是整型变量。
typedef int *apple;
apple a, b; //a和b的类型完全相同,都是指向整型变量的指针。
永远要记住的是,typedef定义的是一种类型而不是变量,不能指望用它来定义一个变量