前言
紧接着预处理详解(上卷),接下来我们来讲宏(隶属于预处理详解系列)。
#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
所以,宏其实是有参数的,这是与我们之前的#define定义常量很不同的一点。
宏的声明方式:
#define name(parameter-list) stuff
parameter-list是一个由逗号隔开的符号表,它们可能出现在stuff中。
与函数不同的地方在于,宏的参数是没有类型的。
宏运行把参数列表里的东西替换到内容(stuff)里。
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
演示:
比如我们现在实现一个宏,求平方。
SQUARE就是宏的名字,n是宏的参数,n*n是宏的内容。可以看到,参数出现在宏的内容中。
编译器对其预处理后是这样的:
#include<stdio.h>
#define SQUARE(n) n*n
int main()
{
int x = 0;
scanf("%d", &x);
int ret = x*x;//替换了
printf("%d\n", ret);
return 0;
}
理解:
宏和函数
我们可以感受到,宏和函数很相似,如果写成函数是这样:
#include<stdio.h>
int square(int n)
{
return n * n;
}
int main()
{
int x = 0;
scanf("%d", &x);
int ret = square(x);
printf("%d\n", ret);
return 0;
}
对于函数来说,要有函数名,参数,返回类型,函数体;对于宏来说,要有宏的名字,参数,宏的体(内容),不过没有参数类型、返回类型。
宏适合完成相对简单的任务。因为当任务复杂时,放进括号(函数的{})里会更方便观看。
宏与括号
现在,如果我们将参数改为5+1,会发生什么?
可以看到我们是得不到想要的结果36的,而是结果为11。
这是因为它是这么替换的:
#include<stdio.h>
#define SQUARE(n) n*n
int main()
{
int ret = 5 + 1 * 5 + 1;
printf("%d\n", ret);
return 0;
}
直接替换,而不是算成6后再替换。
可以这样修改:
所以,在写宏的时候,不要吝啬括号。
再举一个例子,下面的代码是经不起考验的:
实现一个宏,求一个数的2倍:
我们得不到想要的100,而是得到55。这是因为替换后是这样的:
#include<stdio.h>
#define DOUBLE(x) x+x
int main()
{
/*int n = 0;
scanf("%d", &n);*/
int ret = 10 * 5 + 5;
printf("%d\n", ret);
return 0;
}
也是因为是直接替换。
这时应该这样改:
再次重申这个忠告:在写宏的时候一定不要吝啬括号。
用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符(前一个例子)或邻近操作符(后一个例子),产生不可预料的相互作用。
带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
举个例子说明什么是副作用:
#include<stdio.h>
int main()
{
int a = 10;
int b = a + 1;//b=11,a=10。无副作用
int b = ++a;//b=11,a=11。有副作用
return 0;
}
如果我们的宏的参数是带有副作用的呢?会发生什么呢?
当宏参数在宏的定义中出现超过一次时,如果参数带有副作用,那么在使用这个宏的时候可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
演示:
#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
//写一个宏,求两个数的较大值
int main()
{
int a = 10;
int b = 20;
int m = MAX(a, b);
printf("%d\n", m);
return 0;
}
(x)>(y)?(x):(y)是如果(x)大于(y)表达式结果就为(x),否则表达式结果为(y)。
我们可以看到,宏的参数x、y都在宏体内出现了两次。如果现在我们传的是a++,b++,此时算出的m是多少呢?
这是替换后:
int m = ((a++)>(b++)?(a++):(b++));
怎么算呢?
从左向右依次计算,a++是后置++,先试用后++,所以(a++)表达式结果是10,(b++)是20,但是a和b都会++一次,因为10<20,后面的表达式(a++)不会执行,执行的是(b++),b此时已是21,先使用21再++,m得到的是21。真个表达式结束后a变成了11,b变成了22。
如果写成函数,则是这样的:
a++ b++是先使用再++,所以传给函数形参的值是10和20,最后得到的m也是正确的结果。最后打印的a和b也是分别++一次的结果。
但因为宏使用的是替代到宏体内的方式,如果宏体内同一个参数出现多次,++也会出现多次。这种行为是非常危险的。
所以我们在写宏的时候,最好不要传带有副作用的参数,否则产生的结果可能不是我们希望的。
至此,上卷内容结束,祝阅读愉快,敬请期待下卷^_^