2.3.2 带参数的宏
下面介绍带参数的宏在定义时的一些注意事项。有不少读者在编写程序求两数之和时通常会使用下面的方法。
include<stdio.h>
define SUM(x,y)x+y
void main()
{
int x=6;
int y=9;
int s=SUM(x,y);
printf("x+y的值为:%d\n",s);
return;
}
运行结果:
x和y中较大的数为:15
上述代码此时没有任何问题,但是适当修改一下代码。将“int s=SUM(x,y)”修改为“int s=SUM(x,y)*10”。此时的运行结果为:
x+y的值再乘以10为:96
我们发现,结果跟想要的不相符,本意是先求x+y的值,再乘以10,结果应该为150,而这里得到的是96。还是通过宏扩展来查看出错的原因,将“int s=SUM(x,y)10;”扩展为“int s=x+y10;”,我们发现,扩展之后的表达式跟我们的初衷相差甚远。可以进一步修改上面的宏定义的实现方法,将其中的宏定义“#define SUM(x,y)x+y”修改为“#define SUM(x,y)(x+y)”,此时的运行结果为:
x+y的值再乘以10为:150
这时得到的就是我们想要的结果。只是在宏定义部分加了一个括号,以保证在进行宏扩展时x+y是一个整体,不会被拆开。
再来看括号在宏定义中的另一种使用方法。在编写求两个数之差的绝对值的时候,不少人会采用以下宏定义的实现方法。
include<stdio.h>
define SUB_ABS(x,y)x>y?x-y:y-x
void main()
{
int x=-6;
int y=-9;
int abs=SUB_ABS(x,y);
printf("x和y之差的绝对值为:%d\n",abs);
return;
}
运行结果:
x和y之差的绝对值为:3
运行结果是我们想要的3,乍一看,上面的宏定义没有什么问题,现在一步步地找出它存在的问题。修改上面的代码,将其中的“int abs=SUB_ABS(x,y);”修改为“int abs=SUB_ABS(x+y,x-y);”。此时的运行结果为:
x+y和x-y之差的绝对值为:0
这时候就出现问题了,0不是我们想要的结果,动手算算就知道得到的结果应该为18,还是用宏扩展的老方法,将“int abs=SUB_ABS(x+y,x-y);”扩展为“int abs=x+y>x-y?x+y-x-y:x-y-x+y;”,宏扩展后的结果显然是0。所以应该将其宏定义“#define SUB_ABS(x,y)x>y?x-y:y-x”修改为“#define SUB_ABS(x,y)(x)>(y)?(x)-(y):(y)-(x)”。此时的运行结果为:
x+y和x-y之差的绝对值为:18
这时得到的才是正确的结果,是不是这样的宏定义就完全正确呢?当然不是的,如果将其中的“int abs=SUB_ABS(x+y,x-y);”修改为“int abs=SUB_ABS(x+y,x-y)*0;”,此时的运行结果为:
x+y和x-y之差的绝对值乘以0的值为:3
通过上述结果我们就发现上面的宏定义仍然存在问题,相信这时读者应该知道问题的所在了,因为没有使用()将条件表达式表示成为一个整体,所以出现了错误的结果3。进一步修改上面的代码,将其中的宏定义“#define SUB_ABS(x,y)x>y?x-y:y-x”修改为“#define SUB_ABS(x,y)((x)>(y)?(x)-(y):(y)-(x))”,此时的运行结果为:
x+y和x-y之差的绝对值乘以0的值为:0
这时得到的就是想要的结果。
以上从不同方面分析了带参数的宏定义的注意事项。在讲解带参数的宏定义时候,特别提到了关于参数替换的问题。在此,同样通过修改上面的代码来分析。
include<stdio.h>
define SUB_ABS(x,y)((x)>(y)?(x)-(y):(y)-(x))
void main()
{
int x=-6;
int y=-9;
int abs=SUB_ABS(++x,y);
printf("++x和y之差的绝对为:%d\n",abs);
return;
}
运行结果:
++x和y之差的绝对值为:5
上述代码的意思是求-5和-9之差的绝对值,正确的结果应该为4。下面进行宏扩展来查看出错的原因,将“int abs=SUB_ABS(++x,y);”扩展为“int abs=((++x)>(y)?(++x)-(y):(y)-(++x));”后可以发现,不管输入的x和y之间是什么样的大小关系,x自加运算都执行两次,比预期多执行了一次,所以最终得到的是错误的结果。如果对参数进行替换,例如:
include<stdio.h>
define SUB_ABS(x,y)({typeof(x)_x=x;typeof(y)_y=y;(_x)>(_y)?(_x)-(_y):(_y)-(_y);})
void main()
{
int x=-6;
int y=-9;
int abs=SUB_ABS(++x,y);
printf("++x和y之差的绝对为:%d\n",abs);
return;
}
在Linux环境下采用gcc进行编译的运行结果:
++x和y之差的绝对为:4
由于VC++6.0不支持typeof操作符,所以在Linux环境下使用gcc编译运行时,typeof操作符的功能是得到变量的数据类型,这时得到的结果才与我们的意图相符。
所以在代码中要特别注意带参数宏的使用,否则可能带来一些意想不到的错误,为代码调试带来很多的麻烦。当然,最好的方法就是采用宏扩展的方式来看看是否存在宏定义的错误。
从上面对宏定义的常见错误分析可以看出,在使用宏定义的时候尤其要注意括号的灵活使用,如果不小心使用,可能给我们的程序带来意想不到的结果。同时,由于宏定义不进行语法检测,所以相对来说进行查错的难度就大大地增加了。在定义带参数的宏定义时,需要注意参数是否涉及自加自减运算,如果代码中的参数可能涉及自加自减运算,那么最好进行参数的替换,以免自加自减运算对运行结果带来影响。