#define是一个宏定义命令,是预处理命令的一种,就是给一个内容起一个别名,如果在程序中出现了这个别名,会自动替换为该内容,该步骤在编译阶段就已经完成,这种替换就是简单的文本替换,不涉及类型匹配问题。
C语言宏定义的用法为 #define 宏别名 字符串
#表示这是一条预处理命令,在预处理阶段进行文本替换。这里的字符串就是一般意义上的代码文本,不是字符串类型。字符串的内容可以是代码,表达式,数字,函数等。本文讲解的主要内容就是字符串为函数的情况,也就是宏函数。
函数
假如我们需要写一个两数相加的函数,如果碰见不同类型的数据,需要写很多个具体类型的返回值和参数列表。
int add(int a,int b)
{
return (a + b);
}
float add(float a,float b)
{
return (a + b);
}
如果继续写下去,程序中就会出现大量功能相同的函数,下边提到的宏函数就可以解决这个问题。
宏函数
使用#define宏定义时可以带有参数,所以被叫做宏函数。宏函数的声明格式为:
#define 宏名(参数列表) (宏体)
宏名
是我们给宏函数起的名字,参数列表
是宏函数接受的参数,宏体
则是宏展开后要替换的内容。需要注意的是,宏体
通常要用括号( )
包围起来,以避免可能的运算符优先级问题。值得注意的是宏函数只做文本替换,不做计算,这一点很重要。宏函数不会为形参分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
当我们使用宏函数再去实现两数相加求和时可以写为以下形式:
#define add(a,b) ((a) + (b))
printf("%d\n",add(1,3));
//在预处理阶段,上条语句会被替换为以下内容(文本替换)
printf("%d\n",((1) + (3)));
可以看到,宏函数在调用时和函数的调用方法类似,但是要和函数区分开,宏定义只做了文本的替换。
函数和宏函数的区别
从上面可以看到函数式宏在某些时候可以替代函数的作用,它们的区别如下:
函数式宏是在编译时展开并填入程序的,而函数定义则需要为每个形参都定义各自的数据类型,返回值类型也只能为一种。函数更为严格。
函数默默的为我们进行一些复杂的操作,比如:参数传递(将实参值赋值给形参),函数调用和函数返回操作,返回值的传递;而函数式宏只是做宏展开,并不做上述处理。
函数式宏能使程序的运行速度提高,但是当函数中有大量的宏替换的时候,又会使得程序变得臃肿。原理是宏函数只做替换,并没有类似函数调用的跳转、参数出入栈等操作,自然会提高函数的运行速度。这种运行速度加快体验在大量使用的时候尤为明显。
宏在使用的时候必须小心谨慎,避免出现问题。这一点是由宏的本身特性决定,即只做替换不做计算。
特性 | 宏/宏函数 | 函数 |
---|---|---|
处理阶段 | 预处理阶段(编译前) | 运行时调用 |
类型检查 | 无,仅文本替换 | 有,参数和返回值需明确类型 |
内存占用 | 不分配内存(替换后代码嵌入) | 形参和局部变量占用栈内存 |
执行效率 | 无调用开销,执行快 | 需保存/恢复现场,有调用延迟 |
代码体积 | 多次替换可能导致代码膨胀 | 复用代码,体积较小 |
调试支持 | 不可调试(替换后源码丢失) | 可调试 |
宏函数的弊端
1、隐式的增加操作:
#define sqr(a) ((a) * (a))
sqr(a++);
//被替换为
((a++) * (a++));
我们的本意是求a的平方再使a自增一次(a++代表先使用a的值再进行自增),但是实际经过替换之后a自增了两次,就造成了隐式错误。事实上上述代码在不同的编译器内的运行结果是不同的,甚至同一编译器的不同版本结果也不同,原因是编译器执行的顺序不同。若a = 1,有些编译器先执行左边的a++,左边为1,之后a变为2,再执行右边的a++,所以结果是1 * 2,a最终为3;有些编译器先执行右边的a++,最终结果就是2 * 1,a最终为3;有些编译器是左右两边的a++均先返回值,再执行相乘,结果就是1 * 1,a最终结果为3。
2、宏函数变为了无参宏定义:假如我们在宏函数中不小心多打了一个空格,变为以下形式。
#define add (a,b) ((a) + (b))
此时原本的宏函数就变为了无参的宏定义,代表将add这个文本替换为(a,b) ((a) + (b))。在以下例子中使用这个宏定义,我们会发现,替换之后的代码全乱套了,根本不是我们想实现的功能。
#include <stdio.h>
#define add (a,b) ((a) + (b))
int main()
{
int a,b;
a = 3;
b = 5;
printf("%d\n",add(a,b));
//被替换为以下内容
printf("%d\n",(a,b) ((a) + (b))(a,b));
}
3、宏函数导致的数学运算问题
#include <stdio.h>
#define add(a,b) a + b //不规范的宏函数声明
int main()
{
int a = 3,b = 5,c = 4,d = 6;
printf("%d\n",add(a,b) * add(c,d));
//被替换为以下内容
printf("%d\n",a + b * c + d);
}
可以看到,在预处理阶段进行了文本替换之后,实际的运算顺序和我们预想的效果不同,导致了一个数学运算问题。所以在宏函数声明时必须要给每个参数和整体加上括号,以防止可能的运算顺序问题。
#include <stdio.h>
#define add(a,b) ((a) + (b)) //使用规范的宏函数声明
int main()
{
int a = 3,b = 5,c = 4,d = 6;
printf("%d\n",add(a,b) * add(c,d));
//被替换为以下内容
printf("%d\n",((a) + (b)) * ((c) + (d)));
}
总结,我们在定义和使用函数式宏的时候一定要每个参数以及整个表达式都用()括起来,而且要注意格式问题,不要多打空格,注意避免其产生副作用。
带有多条语句的宏函数
宏函数内可以包含多条语句,但是宏定义要求只能替换同一行内的文本内容,我们可以使用\符号,这个符号代表将下一行也视作本行内容。
#define handle_error(cmd) \
{ \
perror(cmd); \
exit(EXIT_FAILURE); \
} \
以下述代码为例,使用该宏函数。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(cmd) \
{ \
perror(cmd); \
exit(EXIT_FAILURE); \
} \
int main()
{
int tmp = 0;
tmp = pthread_create(&pthread1_id,NULL,thread1,NULL);
if(tmp < 0)
handle_error("thread_create");
else
{}
}
经过替换之后变为以下内容
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(cmd) \
{ \
perror(cmd); \
exit(EXIT_FAILURE); \
} \
int main()
{
int tmp = 0;
tmp = pthread_create(&pthread1_id,NULL,thread1,NULL);
if(tmp < 0)
{
perror(cmd);
exit(EXIT_FAILURE);
};
else
{}
}
很明显,if下的复合语句’}‘后的’;‘会被认为是空语句,if和else之间多出了一个空语句,那么此时else再去找它上面的那个if的时候就找不到了,因此就会报错。
我们可以在调用时后边不加分号,但是这样就违反了平常的编码习惯。我们可以使用逗号表达式来解决,在逗号表达式中的表达式都会执行,但是最终整个表达式的值等于最后一个逗号后边的表达式的值。像我们上边的情况,我们不需要整个表达式的值,只需要所有表达式都会执行即可。
像下边这个解决方法,就将多条语句合并为了一个表达式,变为了
if()
语句;
else
语句;
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(cmd) \
( \
perror(cmd), \
exit(EXIT_FAILURE) \
) \
int main()
{
int tmp = 0;
tmp = pthread_create(&pthread1_id,NULL,thread1,NULL);
if(tmp < 0)
handle_error("thread_create");
else
{}
//替换之后变为以下内容
if(tmp < 0)
(perror(cmd),exit(EXIT_FAILURE));
else
{}
}