一个完美MAX宏的诞生及进化

简介: 一个完美MAX宏的诞生及进化

  我们在写代码的时候经常会需要比较两个值的大小,进而获取最大值或最小值,而最常用的方法是把其写成标准的宏,方便移植的同时也增加了代码的阅读性。

       当然,当你在面试的时候也很有可能面试官会让你写一个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宏发生了翻天覆地的变化,当然这些也都是血泪史啊,在项目中积累下来的经验,在大坑中获取到的宝藏。

相关文章
|
5月前
|
编译器 开发工具 C语言
探索STM32的无限可能:应用与发展
探索STM32的无限可能:应用与发展
42 2
|
5月前
|
机器学习/深度学习 人工智能 分布式计算
人工智能平台PAI问题之loss为负数如何解决
人工智能平台PAI是指阿里云提供的机器学习平台服务,支持建模、训练和部署机器学习模型;本合集将介绍机器学习PAI的功能和操作流程,以及在使用过程中遇到的问题和解决方案。
|
5月前
|
安全 程序员 C++
【C++ 基本知识】现代C++内存管理:探究std::make_系列函数的力量
【C++ 基本知识】现代C++内存管理:探究std::make_系列函数的力量
189 0
|
人工智能 自然语言处理
如何理解人工智能领域 LLM 的 No notion of time or chronological order 这一局限性?
如何理解人工智能领域 LLM 的 No notion of time or chronological order 这一局限性?
|
机器学习/深度学习 监控 自动驾驶
STDC升级 | STDC-MA 更轻更快更准,超越 STDC 与 BiSeNetv2
STDC升级 | STDC-MA 更轻更快更准,超越 STDC 与 BiSeNetv2
422 0
STDC升级 | STDC-MA 更轻更快更准,超越 STDC 与 BiSeNetv2
|
数据可视化 API 图形学
3DMax和Maya到底哪个更好用?
有很多小伙伴常常问3Dmax跟maya有什么区别呢?这两个软件到底哪个更好用?这个问题今天就来跟大家说说区别到底在哪里。 **3DMax**
280 0
3DMax和Maya到底哪个更好用?
|
存储 C++
C++中的const进化
C++中的const进化
64 0
|
Rust 机器人 编译器
Rust 公布 2024 年路线图:重点涉及三个方向
Rust 公布 2024 年路线图:重点涉及三个方向
462 0
|
机器学习/深度学习 Web App开发 人工智能
LeCun带两位UC伯克利华人博士提出「循环参数生成器」,一个参数重复用!
近日,LeCun带领两位来自UC伯克利的华人博士共同发表了一份关于如何减少参数冗余问题的论文,团队提出的RPG循环参数生成器,在减少骨干参数的同时,也依然能获得比SOTA更好的性能。
151 0
LeCun带两位UC伯克利华人博士提出「循环参数生成器」,一个参数重复用!
|
定位技术 人机交互 Go
还记得Magic Leap么,它最终证明AR消费级眼镜是个伪命题
现阶段直接面向消费者不是个明智的选择。