C语言宏
本文主要讲解《C陷阱与缺陷》第六章中宏的使用。
了解宏,我们首先得了解编译器编译C语言程序的过程:
其中预处理器工作有:
(1) 文件包含:可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2) 条件编译:预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外。
(3) 宏展开:预处理器将源程序文件中出现的对宏的引用展开成相应的宏定义。
预处理中宏的作用:
(1)方便程序的修正:将某个特定数量在程序中出现的所有实例统统加以修改;
(2)提高程序的运行效率:C语言在实现函数调用时会带来重大系统开销,宏可以实现一种这样的程序块:它看上去像函数,但却没有函数调用的开销。
宏有有两种定义格式:
(1) 简单的宏定义:#define <宏名> <字符串> //对应宏作用1
#define PI 3.1415926
#define PI 3.14
(2) 带参数的宏定义:#define <宏名> (<参数表>) <宏体> //对应宏作用2
#define f(x) x
宏应该注意的地方:
(1)不能忽视宏定义中的空格:
例子:下面宏定义中 f 是否带了一个参数?
#define f (x) ((x)-1)
答案两种:f(x) = ((x) - 1) 或者 f=(x) ((x)-1)
在上述定义中f = (x) ((x) - 1)是正确的(虽然无法使用),因为 f 和后面的(x)之间多了一个空格。f (3) = 2;
因此在定义宏的时候,要严格的注重空格的使用,一般来讲带参数的宏,其参数列表与宏名要相连。
(很有趣:定义#define f(x) ((x) - 1), 调用f(3) = f (3) = 2)
(2)宏不是函数,需注重括号的使用:
例子:
#define abs(x) x > 0 ? x : -x //正确使用:#define abs(x) (((x)>0) ? (x) : (-x))
对abs(a-b)求值,宏被展开为:a-b >0 ? a-b : -a-b
例子中的-a-b相当于(-a)-b,而不是我们期望的-(a-b);因此,在定义宏的时候我们最好把每个参数都用括号括起来,整个表达式也括起来。
(3)宏不是类型定义:
看下面代码:
#define NEWTYPE struct newType NEWTYPE a; NEWTYPE b,c;
我们只需要改动一行代码,就可以改变a,b,c的类型,宏的这种用法有一个有点:可移植性,得到了所有C编译器的支持,但是,我们最好还是用typedef来定义:
typedef struct newType NEWTYPE
因为使用宏会带来意想不到的错误,例如:
#define T1 struct foo* typedef struct foo *T2;
从定义上看,T1和T2的概念上完全符同,都是指向了foo的指针。但是,当我们试图使用它们来声明多个变量时,就会出现问题:
T1 a, b; //宏扩展:struct foo * a, b; T2 a, b; //typedef定义:struct foo *a, *b;
可以看见使用宏扩展并不能得到我们想要的东西。
所以,使用类型定义的时候,我们最好不要使用宏,应该使用typedef。
对于宏的讲解结束,让我们回过头看看 宏应该注意的地方(1)
#define f (x) ((x)-1)
对于表达式 (x) ((x) -1) 是否能够成为一个合法的C表达式呢?
给出一种合理解:
我们定义:typedef int x;
在这种情况下:(x) ((x) - 1) 等价于 (int) ((int) - 1)
即将-1转换为int类型两次!