C语言基础–宏函数
1. 函数和数据类型
-
函数式宏(宏函数)和函数类比会更加灵活,我们通过两个例子来看一下。
函数
#include <stdio.h> int sqr_int(int x) { return x*x; } double sqr_double(double x) { return x*x; } int main(int argc , char *argv[]) { int n; double x; printf("请输入一个整数:\n"); scanf("%d",&n); printf("%d的平方是:%d\n",n,sqr_int(n)); printf("请输入一个实数:\n"); scanf("%lf",&n); printf("%lf的平方是:%lf\n",n,sqr_double(n)); return 0; }- 当然了,如果计算long int,或者long long int型的数据类型的话我们还得编写该类型的函数。可以 遇见,如果这样下去代码中将会充斥着大量功能相近,名称相似的函数,看起来非常糟糕。
- 下面来看一下函数式宏的实现:
函数式宏
#include <stdio.h> #define sqr(x) ((x)*(x)) int main(int argc,char *argv[]) { int n; double x; printf("请输入一个整数:\n"); scanf("%d",&n); printf("%d的平方是:%d\n",n,sqr(n)); printf("请输入一个实数:\n"); scanf("%lf",&n); printf("%lf的平方是:%lf\n",n,sqr(n)); return 0; }- 可以看到一个简单的宏函数就实现了几个函数才能实现的事情,当然了宏函数可不止这点儿能耐。
- 本例中类似sqr(@) —>展开为((@)*(@))的形式就称为宏展开
- 当调用该宏函数的时候,就会在调用位置将该宏函数展开为上述形式。
- 值得注意的是宏定义只做替换不做计算,这一点很重要,后面会再提到。
2. 函数和函数式宏
- 从上面可以看到函数式宏在某些时候可以替代函数的作用,它们的区别如下:
-
函数式宏是在编译时展开并填入程序的
-
而函数定义则需要为每个形参都定义各自的数据类型,返回值类型也只能为一种。函数更为严格。
-
函数默默的为我们进行一些复杂的操作,比如:
- 参数传递(将实参值赋值给形参)
- 函数调用和函数返回操作
- 返回值的传递
而函数式宏只是做宏展开,并不做上述处理。
-
函数式宏能是程序的运行速度稍微提高一点儿,但是当函数中有大量的宏替换的时候,又会使得程序变得臃肿。(原理是宏函数只做替换,并没有类似函数调用的跳转、参数出入栈等操作,自然会提高函数的运行速度。这种运行速度加快体验在大量使用的时候尤为明显)
-
宏在使用的时候必须小心谨慎,避免出现问题。这一点是有宏的本身特性决定,即只做替换不做计算。举例来说明:
宏的副作用
情况一:
#define sqr(a) ((a)*(a))若调用该函数式宏计算sqr(a++),展开后就变为:((a++) * (a++)),可以发现a执行了两次自增操作。这就会造成隐形的错误,比如我只是想将a自增1后在求其平方,但是结果却并非我们所想。
情况二:
宏定义与宏函数
假如我们在sqr 和’('之间多敲了一个空格,如下
#define sqr (a) ((a)*(a))那么此时函数式宏就变成了,宏定义了,也成对象式宏。即sqr会被编译器替换成(x) (x)*(x)
在定义宏函数的时候注意宏函数名和’('之间不能有空格。
情况三:
#define sum(x,y) x + y //注意:不规范的函数式宏的定义 //调用 z = sum(a,b) * sum(c,d); //编译器将其展开后就变为: z = a + b * c + d; //这样是不是偏离了我们的本意因此,我们在定义函数式宏的时候与一定要每个参数以及整个表达式都用()括起来,就不会出错了。上面的就可以改为
#define sum(x,y) ((x) + (y)) //正确的定义方法 //调用 z = sum(a,b) * sum(c,d); /编译器将其展开后就变为: z = ((a) + (b)) * ((c) + (d));总结,在定义和使用函数式宏的时候要注意避免其产生副作用
3. 不带参数的函数式宏
-
函数式宏也可以像函数那样不带参数
#define my_print() (printf("你好啊!\n"))
4. 函数式宏和逗号表达式
- 下面将说明函数式宏使用时的一个重要技巧
#include <stdio.h>
#define puts_alert(str) {putchar('\a');puts(str);}
int main(int argc,char *argv[])
{
int n;
printf("请输入一个整数:");
scanf("%d",&n);
if(n)
puts_alert("这个数不是0.");
else
puts_alert("这个数是0.");
return 0;
}
-
当我们运行这个程序的时候会报错,无法运行。提示else 缺少if
因为将该函数式宏展开后就变为:
#include <stdio.h>
#define puts_alert(str) {putchar('\a');puts(str);}
//当然了,如果一行定义不下,有时候为了美观我们也这样写,'\'表示下一行还有内容
#define puts_alert(str) {putchar('\a'); \
puts(str);}
int main(int argc,char *argv[])
{
int n;
printf("请输入一个整数:");
scanf("%d",&n);
if(n)
{putchar('\a');puts(str);};
else
{putchar('\a');puts(str);};
return 0;
}
- 很明显,if下的复合语句’}‘后的’;‘会被认为是空语句,那么此时else再去找它上面的那个if的时候就找不到了,因此就会报错。当然了函数式宏中的’{}'也不能少,若少了又会报其他的错误。怎么解决呢?可以思考一下。当然了,也可以参见下面一节
5. 函数式宏和逗号表达式
- 上面一节我们看到了当函数式宏中有多条需要执行的语句的时候我么遇到了麻烦,下面各处解决方法:
#include <stdio.h>
#define puts_alert(str) (putchar('\a'),puts(str))
int main(int argc,char *argv[])
{
int n;
printf("请输入一个整数:");
scanf("%d",&n);
if(n)
puts_alert("这个数不是0.");
else
puts_alert("这个数是0.");
return 0;
}
//在if处展开
if(n)
(putchar('\a'),puts(str));
else
(putchar('\a'),puts(str));
-
这里有个知识点,逗号表达式。逗号运算符:
表达式a,表达式b —> 按顺序判断a和b,整个表达式最终生成表达式b的判断结果。当然了,若有多个表达式(整个逗号表达式的值为最后一个表达式的值),可以以此类推。
举个例子:
int a = 3,b = 4,z = 0;
z = ++a,++b;
//执行时a的值会自增1,b的值也会自增1,最终将b自增后的值赋给变量z
//此时z的值为5,也就是最终整个逗号表达式的结果。
可以看到:整个逗号表达式中每个语句都会被计算到。
如果想要了解更多的逗号表达式的知识可以参考下我的另一博文:
逗号运算符与逗号表达式
6. 总结
- 函数式宏在使用时很方便,正确使用的话不仅可以使得我们的程序变得简洁,而且可以提高我们程序的运行速度。
- 但是这种方式有时也很容易出错,所以我们在使用的时候一定要非常小心,避免出错。
本文深入探讨C语言中的宏函数,对比宏函数与普通函数的优劣,讲解宏函数的定义、使用及其可能带来的副作用,同时提供避免副作用的策略。文章通过实例展示了宏函数在代码简化和运行效率提升方面的作用。
258





