在代码开发过程中,有一些常用或者可以通用的功能或者代码段,实现这些功能,既可以写成函数,也可以封装成为宏定义。那么究竟是用函数好,还是宏定义好?这就要求我们对二者进行合理的取舍。
先看宏
宏: #define 机制包括了一个规定,允许把参数替换到文本中,这种实现常常称为宏,或者宏定义。
宏对变量的副作用:
#define SQUARE(x) (x * x) //常见的求平方
然后求平方
SQUARE(5)
预处理器就会用 5 * 5 这个表达式替换上面的表达式,结果是 25,符合你的预期。
但是请看下面一个:
int a = 5;
printf(" (5 + 1) square: %d\n", SQUARE( 5 + 1));
结果是:11 不符合你的预期
实际在执行时, 使用 5 + 1 * 5 + 1 替换了上面的表达式, 结果就是11, 不符合你的预期, 这种是最常见的相邻的操作符优先级, 大于你传入参数需要优先执行操作符的优先级.
类似的还有 a++ 这样的操作, 都会带来副作用.
宏对参数的副作用,在使用自定义函数时就不会产生:
int get_square(int a)
{
return a*a;
}
调用 get_square(5+1)时, 不会产生参数的副作用, 程序执行时, 会先算出5+1的和, 然后再传入函数去执行。
先看一下《C与指针》中的宏定义“函数”与自定义接口的区别:
宏与函数的区别 | ||
属性 | #define 宏 | 函数 |
代码长度 | 每次调用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度将大幅增加 | 函数代码只出现在一个地方,每次调用这个函数时,调用的都是那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用/返回的额外开销 |
操作符优先级 | 宏参数的求值是在所有周围下上文环境里,除非给它们加上括号,否则邻近操作符的优先级,可能会产生不可预测的结果 | 函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值更容易预测。 |
参数求值 | 参数每次用于宏定义时。它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果。 | 参数在函数被调用前只求值一次,在函数中多次使用参数,并不会导致多种求值过程,参数的副作用并不会造成任何特殊问题 |
参数类型 | 宏与类型无关,只要参数的操作是合法的,它可以适用于任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们的任务是相同的 |
我们常用到的比较大小,宏定义:
#define MAX( a, b) ( (a) > (b) (a) : (b) )
自定义接口实现就是:
int max( int a, int b)
{
return (a > b a : b)
}
区别:
1. 最直观的来讲,自定义的函数已经指定了类型,只能比较两个整数的大小,却不能再去比较浮点数的大小,或者两个ASCII码的大小;而宏定义是不会限定类型的,只要比较的类型一致即可。
2. 自定义接口在程序运行时,会产生临时的堆空间,有临时的空间消耗,如果是递归的话,需要的临时栈空间可能更多;宏定义是在程序运行时,会将宏定义这段代码插入到程序中执行,会有额外的代码段。
3. 自定义接口在调用时,实际的开销要比代码段大,规模更大; 而宏比自定义函数在程序的规模和速度方面,比自定义函数更胜一筹。