函数和#define(宏)的不解之缘
在C语言中,我们常常会使用到函数和define使得我们的解题更加便捷,当我们慢慢了解的东西越来越多了,就可以发现有时候用函数来解决问题比不上宏的高效快速,有时候用宏来解决问题却比不上函数一般逻辑通顺。
那宏和函数到底是谁更胜一筹呢?
宏的使用是在我们代码的编译阶段,当进入编译阶段时,我们定义的宏会找到使用宏的代码段进行直接替换,而函数是不一样的,函数在内存中存在的寿命是伴随整个程序的寿命,就算没有调用该函数,函数还是会在内存中占用空间,但一想,谁又会去创建一个不去使用的函数呢?
而宏是没有类型的所以使用起来非常方便他可以适应所有的数据类型,当我们定义一个函数时,要考虑到数据类型,一种数据类型要单独成为一个个体,而宏只要定义一次,可以使用所有的数据类型,不会有任何代码量的增加,是非常方便的。
我们在认识结构体大小时,不可避免的会遇到偏移量该名词,而计算偏移量的函数offsetof就是利用宏定义的
#define offsetof(structname,memberName) &((struct name*)0)-memberName
看起来相当一个复杂的函数居然用一行代码就可以解决,这不是每个程序员都想得到的吗?但是在实际生活中,这样的情况还是很少见的。
//举一个例子
#define ADD(x,y) x+y
int main()
{
int x=3;
int y=4;
printf("%d",ADD(x+y));
double m=1.7;
double n=3.2;
printf("%d",ADD(m+n));
return 0;
}
该宏非常明确,意思就是两个数相加,如果我们简单这样使用是小看了宏的功能也小看了他的风险。
//还是使用前者的宏
int main()
{
int a=3;
int b=4;
printf("%d",20*ADD(3,4));
return 0;
}
//看起来结果似乎是140
//但是在宏替换过程中
//20*ADD(3,4)-->20*3+4
//结果是64
//和我们预想的大相径庭
我们知道宏是没有类型之分的,也因为没有类型,显得不够严谨,比如说常见的优先符问题,而上题就是因为*的优先级大于+,容易导致你想象不到的错误。使用宏,那注意的东西就多了,虽然看起来一个简简单单的宏,需要注意的地方还真的挺多。
//例一
#include<stdio.h>
#define FUC(x,y) x*y
int main()
{
int m=FUC(3+2,3+4);
printf("%d",m);
return 0;
}
//例二
#include<stdio.h>
#define FUC(x,y,z) return (x>y?x:y)>z?(x>y?x:y):z
int main()
{
int x=3;
int y=4;
int z=5;
int max=FUC(x++,y++,z-3);
printf("%d %d %d",x,y,z);
}
看见以上代码,我们都会以为例题一结果是45,但是大错特错,结果是13,因为在宏替换的过程中,他只是做了一个替换的动作,并没有帮你进行运算,而本来的5*7变成了3+2*3+4也就是无法预料的结果。
例题二也是一样直接进行替换,(3++>4++?3++:4++)>5++?(3++>4++?3++:4++):5++,这种参数也有很大的问题,我们一般称之为副作用参数,因为该类参数,我们无法预料结果是什么,有了更多都不确定性,也就有很高的风险性,在为宏传参时,要避免将此类数据作为参数传递,一般指的副作用参数都是表达式带上++或者–这类计算符号。
以上两种是定义宏经常会遇到的两大错误,而要避免出现这种错误,可以将宏进行改造或者传参时尽量避免使用表达式,更加推荐前一种,后者后患无穷,每次你要使用时,你必须先将结果计算,会大大提高代码量。
前者解决方法给每一个变量加上“()“,如:
(
(
x
)
∗
(
y
)
)
((x)*(y))
((x)∗(y))
将表达式置成这样,提高了安全性,也让人清晰易懂。
而对例一的题目进行计算就可以替换为
#include<stdio.h>
#define FUC(x,y) ((x)*(y))
int main()
{
int m=FUC(3+2,3+4);
printf("%d",m);
return 0;
}
//在编译过程中替换
//((3+2)*(3+4))-->5*7=35
//使得结果更容易预测
对于函数呢,因为拥有了类型而会安全很多,但函数并不是只有这种程度,解决问题时,我们常常会遇到各种错误,遇到错误时,一般进行调试是解决的第一步办法,而对函数进行调试是我们耳熟能详的,对于**宏,是无法调试的!!!**因为函数便于调试我们才得以解决问题,而因为宏无法调试也就不能大量使用。
//宏
#define MUL(x,y,z) x*y*z
//函数
int mul(int x,int y,int z)
{
return x*y*z;
}
使用宏与函数定义同一种方法时,感觉并没有什么不同,但当我们将表达式或者空字符(因为没有类型限制,如果误写了一个非表达式中的字符串)传入时,就会发现错误的存在,而发现错误时,代码过于抽象(虽然以上代码简单易懂),就不可避免使用调试,而洪无法调试,使用宏时就应该加上括号!!!
宏是不能递归的!!!
在编译期间,代码块中使用宏的那一段代码就会进行替换,无法达到使用递归的条件。递归可以让我们的代码更加简明,而递归是一直存在的,因为他伴随着函数的生命,宏在编译期间生命就已经消耗殆尽。函数在使用时,就一直存在一个地方,占用一块空间,而宏是使用几处,替换几处,会加长代码。但有因为在编译期间就被替换所以执行速度比函数要快得多,提高了效率。
在函数和宏进行比较时,我们很容易就可以发现,宏和函数各有所长,在应用中,宏更加使用于简单运算,而函数更适合处理复杂运算,当我们对函数传参时,函数会对该表达式或者变量进行一次运算,所以使用起来更加安全,结果更容易预测。
使用宏和函数的语法也是非常相似,所以一般为了区分函数与宏,我们平时定义宏时会将宏名全部大写,而函数名不会使用全部大写。