C 语言函数宏的单行与多行封装
在c语言开发中,出了使用函数封装代码之外,也经常使用宏来封装一些重要或简洁的代码。
函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销,在实际项目开发中,函数宏的作用很强大。下面介绍函数宏的各种写法:
一、宏函数的单行写法
比如:
#define MAX(a,b) ((a)>(b)?(a):(b))
二、宏函数的多行写法
前面我们介绍的宏函数是单行定义的,那么假设我们的宏函数有多行应该怎么写呢?写法和实际写函数的时候有一点区别,换行的时候需要加反斜杠"\"
分隔,总共有3种方式
①{}
单大括号方式
比如:
#define SWAP(a,b) \
{ \
a=a+b;\
b=a-b;\
a=a-b;\
}
此时如果在非控制语句中调用,则可以正常编译通过,如下:
int main()
{
int x = 1;
int y = 2;
SWAP(x, y);
printf("x=%d, y=%d", x, y);//结果应该是 x=2, y=1
}
但当在控制语句中调用时,比如分支语句if(elseif、else等)如:
if(true)
SWAP(x,y);
else
{
printf("hello world\n");
}
编译器会报错,如下:
上面的语句展开为:
SWAP(x,y);
后面的;使得if的作用域终结了,后续的else当然没有找到与之
匹配的if了。宏函数应该适用于任何语法。
这种函数宏的优缺点:
- 优点:简单粗暴。
- 缺点:不能在无花括号且有分支的if语句中直接调用;(但能够不带;直接调用)
②、do{...}while(0)
或者 if(1){}
流控制块方式
#define SWAP(a,b) \
do{ \
a=a+b;\
b=a-b;\
a=a-b;\
}while(0)
注意:宏中while(0)后没有 ;
号。
另外,do{...}while(0)
为控制流语句,是一条复合语句,在语句块中可以添加参数检测。
例如:
#define SWAP(a,b) \
do{ \
if(a<0 || b<0) break;\
a=a+b;\
b=a-b;\
a=a-b;\
}while(0)
编译器会把do{...}while(0);认为为一条语句。因此,do{...}while(0)方式
的函数宏可以在无花括号且有分支的if语句中直接调用。例如:
这种函数宏的优缺点:
- 优点:支持在无花括号且有分支的if语句中直接调用;支持提前退出函数宏(如参数检查);强制调用时必须使用;。
- 缺点:无返回值,不能作为表达式的右值使用。
③、({})
小括号+大括号方式
({})为GNUC扩展的语法,非C语言的原生语法,封装后形态如下:
#define SWAP(a,b) \
({ \
a=a+b;\
b=a-b;\
a=a-b;\
})
({})既可以用于分支语句中,也可以作为右值,例如:
这种函数宏的优缺点:
优点:支持在无花括号且有分支的if语句中直接调用;有返回值,支持作为表达式的右值。
缺点:不支持提前退出函数宏;非C的原生语法(GCC支持的statement expression语法,用于将多条语句打包成一个表达式),编译器可能不支持。
附:以下使用 ({ })
方法为一个改写 平方和 函数的案例:
// 原函数定义
int add(int x, int y) {
return x*x + y*y;
}
// 宏定义
#define ADD(x, y) ({ \
int _x = (x); \
int _y = (y); \
_x*_x + _y*_y; \
})
// 使用示例
int main() {
int a = 3, b = 4;
int c = add(a, b);
int d = ADD(a, b);
printf("%d %d\n", c, d); // 输出:25 25
return 0;
}
注:宏定义不需要函数的返回值类型,因此需要确定函数的返回值类型并将其包含在宏定义中。
④、总结
综上,在{}、do{...}while(0)和({})这三种函数宏的封装方式之中,应:
- 尽可能不使用
{}
单大括号模式, - 考虑兼容性一般选择使用
do{...}while(0)
流控制语句块模式, - 当需要函数宏返回时可以考虑使用
({})
小括号+大括号模式,或直接定义函数
参考资料:
1. 《c语言函数宏的封装方式有哪几种?》:https://zhuanlan.zhihu.com/p/666071306