带参数的宏定义、函数与内联函数
前言
在实际项目开发,尤其是嵌入式软件项目中,经常可以看到大量宏定义的分布,其中又多以带参数的宏定义为主。对于刚入门的小白来说,这可能也算是C语言的高级用法之一。当然,它的用处也确实相当广泛。
带参数的宏,它的功能类似于函数。在多数场景当中,它的目的也是旨在能够一定程度上取代部分的函数调用。
以上两种方式并存,但各自都有优缺点,也因此,inline函数诞生。它可以说是兼具了函数和带参数的宏定义优缺点于一身的产物。
“存在即合理” --黑格尔《法哲学原理》
一、宏定义
1.基本用法
在预编译过程中无条件文本替换:
#include<stdio.h>
#define OKAY 0
#define OVERFLOW 1
#define UNDERFLOW 2
#define MAX 10
#define MIN 5
void main()
{
unsigned char InputVal = 0;
unsigned char Result = OKAY;
printf("Inpue a new value please: ");
scanf("%d", &InputVal);
if( MAX < InputVal ){
Result = OVERFLOW;
printf("The input is OverFlow !\n");
} else if( MIN > InputVal ){
Result = UNDERFLOW;
printf("The input is UnderFlow !\n");
}else{}
}
在预编译之后,文件中所有出现的宏定义,将分别被对应的数值替代,如,代码中出现MAX 的地方,被10取代,MIN被5取代。
2.带参数的宏定义
以下为一个经典的判断最值的例子:
#define MAX(a,b) ((a)>(b)?(a) : (b))
如果代码中有如下“调用”语句:
int c=MAX(5,3);
编译器预处理后,代码被替换为:
int c=((5)>(3)?(5) : (3));
不难看出,宏定义的使用,无论是否带有参数,其实就是一个纯粹的文本替换的过程。它的出现,在一定程度上,增强开发者或者后续维护者的交互体验,毕竟对于人类来说文字描述更容易理解。但是,它的劣势也是显而易见的:代码量的增加。毕竟再小规模的宏定义,替换的地方越多,也就意味着代码量的膨胀。
宏定义还有一个优势,即“一改全改”,不用查找文件中的所有引用逐一进行修改,只需要需改宏定义不分即可。当然,这也可能带来另一个致命的缺陷:一旦宏定义不准确,错误将是全局性的。
二、函数
1.定义与声明
int Func_Max(int a, int b)
{
if(a > b)
return a;
return b;
}
2.调用
int c=Func_Max(5,3);
不难看出,c的值为5,函数Func_Max同样能实现以上宏定义MAX的功能,只不过,实现的过程有一定差异:
- 首先,函数是通过其函数指针进行调用的,不会进行代码文本的替换,因此代码量不会有过多的冗余产生;
- 函数在执行之前,首先会对返回值和参数列表进行安全性检查,并在调用时为它们分配一定的堆栈空间进行参数的暂存。
三、内联函数–inline
像static、extern 等一样,inline是C++中的一个关键词,可用于修饰函数和C++中类的成员函数:
inline int Func_Max(int a, int b)`
- inline定义的内联函数,函数的代码被放入符号表中,在使用时直接进行替换(像宏一样展开),没有了调用时堆栈资源的开销,效率也很高。
- 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
总结
函数、宏定义各自都有着自己的独特性,在自己的应用场景下,有着举足轻重的作用,不能说不能替代,但是替代之前也需要再三思量。inline函数算是二者的子系,它囊括了函数和宏定义的几乎所有的特点,无论是优点还是缺点,它的使用看似常规,快捷,但也暗藏杀机。当然,如果数量掌握了其用法,就另当别论了。