35.0、C语言——C语言预处理(3) - 宏 与 函数
什么是带有副作用的宏参数:
当宏参数在宏的定义中出现超过了一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果;副作用就是表达式求值的时候出现的永久性效果,例如 ->
x + 1; //不带副作用,x 的值没有被改变
x++; //带副作用,X 的值被改变了
带有后遗症的宏参数使用后->
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main() {
int a = 10;
int b = 11;
int ret = MAX(a++, b++);
printf("%d\n",a);
printf("%d\n", b);
printf("%d\n", ret);
return 0;
}
输出的结果为 11 13 12
这里MAX(a++,b++); 会被替换成 ( ( a++ ) > ( b++ ) ? ( a++ ) : ( b++ ) );
执行顺序 ( a++ ) > ( b++ ) 结果为假 ,然后 a = 11 , b = 12;
然后将 b 的 值赋给 ret = 12; 再进行 b++;
所以 b = 13;
总结:我们一定要记住一点,宏的参数不是算好之后再替换进去,而是直接完全替换进去
宏和函数的对比:
宏通常被应用于执行简单的运算;比如在两个函数中找出较大的一个;
#define MAX(a , b) ( ( a ) > ( b ) ? ( a ) : ( b ) )
那为什么不用函数来完成这个任务?原因有二,如下所示 ->
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多【就是调用函数的开销、返回的开销,可能比函数体中执行的代码开销更大】;所以宏闭函数在程序的规模和速度方面更胜一筹【因为每次调用函数都会有调用前的准备开销,然后执行完函数会有一个调用返回的开销;然而宏在预处理阶段就完成了替换,没有更多的开销】;
2. 更为重要的是函数的参数必须声明为特定的类型;所以函数只能在类型合适的表达式上使用;反之这个宏怎可以适用于整型、长整型、浮点型等可以用于来比较的类型;宏是与类型无关的;
#define MAX1(X,Y) ((X)>(Y)?(X):(Y)) int MAX2(int a, int b) { return a > b ? a : b; } int main() { int a = 10; int b = 11; int ret1 = MAX1(a,b); int ret2 = MAX2(a,b); return 0; }
如上述代码,宏 和 函数 都能找出较大值,但是如果我们此时想要比较两个浮点数的较大值,那么函数 MAX1() 就不能使用了,但是宏 MAX2 依旧可以使用并找出两个浮点数的较大值;
当然和宏相比函数也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中;除非宏比较短,否则可能大幅度增加程序的长度【因为每次调用宏都会去替换成相应的内容,那如果这个内容的代码比较多,那么多次调用宏就会增加程序的长度】;
2. 宏时没法调试的;
3. 宏由于类型无关,也就不够严谨;
4. 宏可能会带来运算符优先级的问题,导致程序容易出错;
宏有时候可以做函数绝对做不到的事情;
比如:宏的参数可以出现类型,但是函数做不到【宏的参数可以是int、char等数据类型】;
#define MALLOC(num,type) (type*)malloc(num*sizeof(type)) int main() { int* p1 = (int*)malloc(10 * sizeof(int)); //方式一 int* p2 = MALLOC(10,int); //方式二 return 0; }
如上述代码,将 malloc() 动态开辟内存函数,用宏去定义过后,是不是更加的方便使用了;
命名约定
·1. 一般来讲 函数 和 宏 的使用语法很相似,所以语言本身没法帮我们区分二者;那我们程序员平时的一个习惯是 ->
把 宏名 全部 大写,函数名 不要 全部大写;
2. #undef
这条指令用于移除一个宏定义;
实例代码如下所示->
#define MAX 100 int main() { printf("MAX = %d\n",MAX); #undef MAX printf("MAX = %d\n", MAX); return 0; }
那么第一条 printf 代码可以执行,但是第二条 printf 代码会报错,因为 MAX 在第二条 printf 语句之前被移除了,所以 MAX 现在是未定义的标识符;