以求两个数中的最大值为例
第一阶:裸奔
#define MAX(x,y) x > y ? x : y
测试程序
int main(void)
{
printf("max=%d",MAX(1,2));
printf("max=%d”,MAX(2,1));
printf(“max=%d",MAX(2,2));
printf("max=%d”,MAX(1!=1,1!=2));
return 0
}
在执行第四句会出现问题,实际运行结果为max=0,和预期结果不一致,这是因为宏展开后变成了:
printf("max=%d”,1!=1>1!=2?1!=1:1!=2);
因为比较运算符>的优先级为6,大于!=(优先级为7),所以在展开的表达式中,运算顺序发生了改变,结果就和预期不—样了.为了避免这种展开错误,我们可以给宏的参数加一个小括号(),防止展开后表达式的运算顺序发生变化。
#define MAx(x,y) (x)>(y) ?(x):(y)
第二阶:为变量加上括号
#define MAx(x,y) (x)>(y) ?(x):(y)
测试程序
int main(void)
{
printf("max=%d",3+MAX(1,2));
return 0;
}
在程序中,我们打印表达式3+MAX(1,2)的值预期结果应该是5实际运行结果却是1宏展开后,我们发现同样有问题。
3+(1)>(2)?(1):(2);
因为运算符 +的优先级大于比较运算符 ,所以这个表达式就为 4>2?1:2,最后结果为1也就不奇怪了。此时我们继续修改宏
#define MAX(x,y) ((x)>(y)?(x):(y))
第三阶:为宏运算加上括号
#define MAX(x,y) ((x)>(y)?(x):(y))
这个宏虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。例如,我们使用下面的代码来测试我们定义的宏。
测试程序
int main(void)
{
int i = 2;
int j = 6;
printf("max=%d",MAX(i++,j++));
return 0;
}
在程序中,我们定义两个变量i和j然后比较两个变量的大小,并作自增运算。实际运行这是因为变量i和在宏展开后,做了两次自增运结果发现max=7,而不是预期结果max=6,导致打印出的值为 7。
C语言编程规范里,使用宏时一般是不允许参数变化的。但是万一碰到这种情况,该怎么办呢?这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂时存储 和的值,然后使用临时变量进行比较,这样就避免了两次自增、自减问题。
第四阶:解决变量中存在运算的问题
#define MAX(x,y)({ \
int _x = x; \
int _y = y; \
_x > _y ? _x : _y; \
})
测试程序
int main(void)
{
int i = 2;
int j = 6;
printf("max-%d",MAX(i++,j++));
return 0;
}
在语句表达式中,我们定义了2个局部变量_x和_y来存储宏参数x和y的值然后使用_x和_y来比较大小,这样就避免了 i 和 j 带来的2次自增运算问题。
第五阶:对变量类型进行适配
在第五阶的宏中,我们定义的两个临时变量数据类型是int型,只能比较两个整型数据。那么对于其他类型的数据,就需要重新定义一个宏了,这样太麻烦了!我们可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小。
#define MAX(type,x,y)({ \
type _x = x; \
type _y = y; \
_x > _y ? _x : _y; \
})
int main(void)
{
int i=2;
int j=6;
printf("max=%d\n",MAX(int,i++,j++));
printf("max=%f\n",MAX(float,3.14,3.15));
return 0;
}
在这个宏中,我们添加一个参数type,用来指定临时变量x和y的类型。这样,我们在比较两个数的大小时,只要将 2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。
第六阶:自动识别变量类型
在上面的宏定义中,我们增加了一个 type 类型参数,来适配不同的数据类型。如何把这个参数也省去。可以使用 typeof 就可以了,typeof是GNUC新增的一个关键字,用来获取数据类型,我们不用传参进去,让 typeof 直接获取。
#define max(x,y)({ \
typeof(x)_x=(x);\
typeof(y)y=(y); \
(void)(&x==&y); \
_x > _y ? _x : _y;\
})
在这个宏定义中,我们使用了 typeof 关键字来自动获取宏的两个参数类型。比较难理解的是(void)(&x= &y); 这句话,看起来很多余,仔分析一下,你会发现这条语句很有意思。它的作用有两个:
一是用来给用户提示一个警告对于不同类型的指针比较,编译器会发出一个警告,提示两种数据的类型不同。
二是两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个 warning,加一个(void)后,就可以消除这个警告。