【C语言】宏详解(上卷)

前言

紧接着预处理详解(上卷),接下来我们来讲宏(隶属于预处理详解系列)。

#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也是分别++一次的结果。

但因为宏使用的是替代到宏体内的方式,如果宏体内同一个参数出现多次,++也会出现多次。这种行为是非常危险的。

所以我们在写宏的时候,最好不要传带有副作用的参数,否则产生的结果可能不是我们希望的。

至此,上卷内容结束,祝阅读愉快,敬请期待下卷^_^

  • 40
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值