所谓宏定义,就是用标识符来表示一些东西,所表示的对象可以是一个数字,也可以是一个字符串,甚至是一段程序。对于#define MAX 100,MAX 就是那个标识符,也称宏名,100 就是替换标识符的内容,也称作宏的定义。
在对C源程序预处理时,代码中的所有被宏定义过的标识符都会被替换成它的定义,这个过程叫做"宏替换"或"宏展开"
简单的宏(对象式宏):#define 标识符 替换列表
#define MAX 5 #define reg register //为关键字register创造一个别名reg #define do_forever for(;;) //一个无限循环函数
替换列表可以包括数值常量、字符常量、字符串常量、关键字、标识符和操作符等,当预处理器遇到一个宏定义时,则会将对应替换列表中的内容替换。
带参数的宏(函数式宏):#define 标识符(参数列表) 替换列表
函数式宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参
函数式宏的参数没有限制,可以是类型名、变量名等,因此可以做到函数所不可能的操作,如下面第三个宏定义和#运算符的例子
#define MAX(x,y) x>y ? x:y #define IS_EVEN(x) ((x)%2 == 0) #define OFFSETOF(StructName, MemberName) (size_t)&(((StructName *)0)->MemberName) //宏定义实现offsetof( )函数,求结构体中某成员的偏移量 //宏定义的参数为类型名
用于宏定义的一些特殊运算符
#运算符
#运算符将宏的一个参数转换成字符串,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了双引号
#include
运算符将宏的一个参数转换成字符,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了单引号
#include
##运算符可将两个片段合成一个片段,并进行替换
#include
#include
#define ADD2(m, n) m++; n++;int main(){ int m = 1, n = 1; if (1) {m--; n--;} else ADD2(m, n); printf("%d %dn", m, n); return 0;}//预计输出:0 0//实际输出:0 1//改正 #define ADD2(m, n) {m++; n++;}
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main(){ int x = 5, y = 8; int z = MAX(x++,y++); printf("%d %d %dn", x, y, z); return 0;}//预计输出:6 9 8//实际输出:6 10 9//改正 避免宏定义中使用带副作用的参数
这些错误都是因为宏定义只进行了简单的文本替换,程序并没有按照假想的逻辑而执行,不过大部分的错误都能通过规范的编程习惯来避免。
实际开发中,我们很少会使用宏定义去处理复杂的事情。
一些C预先定义完成特定功能的宏
宏的本质是进行简单的文本替换,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现,稍不注意可能出现意想不到的错误宏的替换列表可以包含对其他宏的调用,即宏定义允许嵌套预处理器只会替换完整的符号,并不会替换宏的片段,也不会替换双引号之内的宏,如:#define MAX 10不会对BUFFER_MAX中的MAX进行替换宏定义的作用范围通常到出现该宏的文件末尾宏定义可使用#undef指令取消 #define 标识符函数式宏与函数 属性 函数式宏 函数 代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码执行速度相对较快由于存在函数调用与返回,所有可能相对较慢操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制参数类型宏的参数与类型无关,只要对参数的操作合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。调试不可调试可调试递归不可递归可递归
参考书目:《C语言程序设计现代方法 第二版》