一句话解释宏定义
所谓宏定义,就是用标识符来表示一些东西,所表示的对象可以是一个数字,也可以是一个字符串,甚至是一段程序。对于#define MAX 100 ,MAX 就是那个标识符,也称宏名,100 就是替换标识符的内容,也称作宏的定义。 在对C源程序预处理时,代码中的所有被宏定义过的标识符都会被替换成它的定义,这个过程叫做"宏替换"或"宏展开"
宏定义详解
- 简单的宏(对象式宏):
#define 标识符 替换列表
#define MAX 5
#define reg register
#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)
- 用于宏定义的一些特殊运算符
- #运算符
#运算符将宏的一个参数转换成字符串,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了双引号#include<stdio.h>
#define tostring(c) #c
#define PRINT(n) printf("The value of "#n" is %d\n", n);
int main()
{
int a = 0, b = 1;
printf(tostring(a)"\n");
PRINT(a);
PRINT(b);
return 0;
}
- #@运算符
运算符将宏的一个参数转换成字符,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了单引号#include<stdio.h>
#define tochar(n) #@n
int main()
{
int x = 10;
printf("%c = %d", tochar(x), x);
}
- ##运算符
##运算符可将两个片段合成一个片段,并进行替换#include<stdio.h>
#define CAT(m, n) m##n
int main()
{
int num1 = 10;
printf("%d", CAT(num, 1));
return 0;
}
宏定义的常见错误使用
-
#include<stdio.h>
#define ADD1(m, n) m+n
int main()
{
int m = 2, n = 10;
printf("%d\n", 3 * ADD1(m, n));
return 0;
}
-
#define ADD2(m, n) m++; n++;
int main()
{
int m = 1, n = 1;
if (1) {m--; n--;}
else ADD2(m, n);
printf("%d %d\n", m, n);
return 0;
}
-
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
int x = 5, y = 8;
int z = MAX(x++,y++);
printf("%d %d %d\n", x, y, z);
return 0;
}
这些错误都是因为宏定义只进行了简单的文本替换,程序并没有按照假想的逻辑而执行,不过大部分的错误都能通过规范的编程习惯来避免。 实际开发中,我们很少会使用宏定义去处理复杂的事情。
预定义宏
一些C预先定义完成特定功能的宏
名字 | 定义 |
---|
__FILE__ | 被编译的文件名 | __LINE__ | 文件当前的行号 | __DATE__ | 文件被编译的日期 | __TIME__ | 文件被编译的时间 | __STDC__ | 如果编译器符合C标准,其值为 1 |
对宏定义的一些说明
- 宏的本质是进行简单的文本替换,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现,稍不注意可能出现意想不到的错误
- 宏的替换列表可以包含对其他宏的调用,即宏定义允许嵌套
- 预处理器只会替换完整的符号,并不会替换宏的片段,也不会替换双引号之内的宏,如:
#define MAX 10 不会对BUFFER_MAX中的MAX进行替换 - 宏定义的作用范围通常到出现该宏的文件末尾
- 宏定义可使用#undef指令取消
#define 标识符 - 函数式宏与函数
属性 | 函数式宏 | 函数 |
---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 | 执行速度 | 相对较快 | 由于存在函数调用与返回,所有可能相对较慢 | 操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测 | 带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制 | 参数类型 | 宏的参数与类型无关,只要对参数的操作合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。 | 调试 | 不可调试 | 可调试 | 递归 | 不可递归 | 可递归 |
参考书目:《C语言程序设计现代方法 第二版》
|