我们在写代码的时候经常会需要比较两个值的大小,进而获取最大值或最小值,而最常用的方法是把其写成标准的宏,方便移植的同时也增加了代码的阅读性。
当然,当你在面试的时候也很有可能面试官会让你写一个MAX的宏定义来侧面反映你的编程功底,虽说无法考量一个程序员的实际功底有多强,但足以反映出一个程序员的基本素质是否达标。
到这里很多人可能都会想,MAX宏?比较两值的大小?这不是很简单嘛,简直就是信手拈来啊,于是就直接写出了下面的宏:
#define MAX(a, b) a > b ? a : b
如果写成上面这样子,那么恭喜你,你离写出完美的bug又进了一步。
下面我们使用以下代码来测试下上述宏的工作情况来看一下:
#include "stdio.h" #define MAX(a, b) a > b ? a : b int main(void) { printf("MAX(a, b) = %d\r\n", MAX(1!=2, 1==2)); return 0; }
上面代码的输出如下:
MAX(a, b) =0
为什么会产生上面的结果呢?1!=2为真, 1==2为假,MAX的结果应该非0才对呀,为什么会为0?
下面来分析下这个宏,把宏展开来看:
1!=2>1==2?1!=2 : 1==2;
由于比较运算符‘>’的优先级高于‘!=’,因此运算顺序发生了改变,所以结果和我们预期的不一样了,为了避免宏展开后出现优先级的问题,可以把宏的参数加上小括号来防止展开后发生运算顺序错误的问题。
改进后的宏如下:
#define MAX(a, b) (a) > (b) ? (a) : (b)
上面这个宏是MAX的第一次进化,完成第一个华丽的蜕变,但是问题来了,进化后的宏就真的没有问题了吗?
我们再使用测试代码来测试一下这个宏,看他能不能经的住考验!
#include "stdio.h" #define MAX(a, b) (a) > (b) ? (a) : (b) int main(void) { printf("MAX(a, b) = %d\r\n", 2 + MAX(3, 4)); return 0; }
从上述代码中不难看出,我们期望用2加上3和4中的最大的那个值,我们期望的输出结果是6,那就来验证下输出结果的正确性:
使用上述代码测试结果如下:
MAX(a, b) =3
????小朋友,是否有大大的问好?
我们期望的输出是6,实际输出是3,这是为什么呢?
我们继续展开来分析宏,展开后宏如下:
2+ (3) > (4) ? (3) : (4)
展开后是不是一目了然了,因为运算符‘+’的优先级也大于‘>’,因此2+3=5>4成立,因此输出3。
知道这个bug后,怎么修改呢?
接下来再看MAX宏的第二次进化,进化后如下所示:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
我们使用小括号把整个宏定义括起来看作一个整体就可以很好的避免上述的问题。
那到这里就是一个完美宏?这就诞生了?
当然没那么简单,这个宏还是有bug。
我们再使用下面的这段代码测试一下这个宏
#include "stdio.h" #define MAX(a, b) (a) > (b) ? (a) : (b) int main(void) { int a = 2; int b = 3; printf("MAX(a, b) = %d\r\n", MAX(++a, ++b)); return 0; }
上述代码的意图也很明显,想要在a和b自加后再去比较两个值的大小,++a运行后a = 3; ++b运行后b = 4; 因此我们期望的输出值是4
来看下实际输出多少,如下:
MAX(a, b) =5
????
继续宏展开
((++a) > (++b) ? (++a) : (++b))
宏展开后可以看到
当a=2,b=3时,b自加了两次,因此MAX的输出为5。
为了解决上面的问题,MAX宏继续第三次的蜕变、进化:
#define MAX(a,b)({ \ int _a = a; \ int _b = b; \ _a > _b ? _a : _b; \ })
我们再使用测试代码去测试这个宏
#include "stdio.h" #define MAX(a,b)({ \ int _a = a; \ int _b = b; \ _a > _b ? _a : _b; \ }) int main(void) { float a = 2.1; float b = 3.2; printf("MAX(a, b) = %f\r\n", MAX(a, b)); return 0; }
在这段代码中我们期望的输出是3.2,但实际的输出如下:
MAX(a, b) =3
在宏里面我们使用的是int定义的中间变量,因此不管我们传什么参数进去,都会被隐式强制转换成整形数比较,出现上面的输出结果也就不奇怪了,那么怎么解决呢?
为了解决上面的问题就修改一下宏的定义,如下:这也是MAX宏的第四次进化
#define MAX(type,a,b)({ \ type _a = a; \ type _b = b; \ _a > _b ? _a : _b; \ })
给宏增加一个类型参数,我们再次运行上述的测试代码就会发现可以正常的输出最大值3.2了。
在面试的时候如果能写出上面的宏,面试官应该就很满意了,知道你这个小伙子功底不错,适合来我司继续深(tuo)造(fa)
至此,应该没有问题了吧,可以拍拍胸脯说我们的代码是没有bug的,找出一个bug算我输,但是真的是这样吗?
为了完美,对得起标题,我们再使用测试代码去测试这段代码:
#include "stdio.h" #define MAX(type,a,b)({ \ type _a = a; \ type _b = b; \ _a > _b ? _a : _b; \ }) int main(void) { float a = 2.1; int b = 3.2; printf("MAX(a, b) = %f\r\n", MAX(int,a, b)); return 0; }
当传入MAX宏的两个参数类型不同时就会出现问题,我们的宏type到底该写那个呢?上述的代码输出为:
MAX(a, b) =0.000000
发现这完全不是我们期望的输出,是个异常值,但是编译器又不会报错或警告,如果靠程序员在茫茫代码中寻找参数类型的问题,等你找到了,公司怕也要破产咯。
那么怎么避免?
这就迎来了宏的第五次进化
#define max(a, b) ({ \ typeof(a) _a = (a); \ typeof(b) _b = (b); \ (void) (&_a == &_b); \ _a > _b ? _a : _b; })
和上一次进化最大的区别就是省去了type的类型参数,使用typeof来获取传入参数的类型并定义中间变量,这样既优化了宏的参数,也优化了宏的应用场景的多元化。
这样就解决了参数不同带来的问题了吗?
答案是否定的,并没有解决,那么要这个宏,看上去这么复杂干嘛呢?
我们仔细分析下宏的第三句,是整个宏的点睛之笔。(void) (&a == &b); 表面上看上去像是在判断两个参数的地址是否相同,有些人到此就说了,用屁股想想地址也不相同呀,这不是一句废话吗?
其实编译器在判断两个参数的地址是否相同的时候会先对其类型进行判断,如果相同则继续判断地址是否相同,否则编译器会给出一个警告,当程序员误把参数类型混用的时候,这时编译器就会给出警告报警,程序员自然也就可以直接的看到这个问题并处理,总比什么都不显示,让程序员慢慢找要好的多吧!
至此就真的结束了,进化也完成了,前后一对比,MAX宏发生了翻天覆地的变化,当然这些也都是血泪史啊,在项目中积累下来的经验,在大坑中获取到的宝藏。