号将宏定义包起来,这样就避免了当一个表达式同时含有宏定义和其它高优先级运算符时,破坏整个表达式的运算顺序。如果你能写到这一步,说明你比前面那个面试合格的同学强,前面那个同学已经回去等消息了,我们接着面试下一轮。
良好
上面的宏,虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。比如,我们使用下面的测试程序来测试我们定义的宏:
#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,然后比较两个变量的大小,并作自增运算。实际运行结果发现 max = 7,而不是预期结果 max = 6。这是因为变量 i 和 j 在宏展开后,做了两次自增运算,导致打印出 i 的值为7。
遇到这种情况,那该怎么办呢? 这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂储 i 和 j 的值,然后进行比较,这样就避免了两次自增、自减问题。
#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次自增运算问题。
你能坚持到了这一关,并写出这样自带 BGM 的宏,面试官心里可能已经有了给你 Offer 的意愿了。但此时此刻,千万不要骄傲!为了彻底打消面试官的心理顾虑,我们需要对这个宏继续优化。
优秀
在上面这个宏中,我们定义的两个临时变量数据类型是 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个数据的类型作为参数传给宏,就可以比较任意类型的数据了。如果你能在面试中,写出这样的宏,面试官肯定会非常高兴,他一般会跟你说:稍等,待会 HR 会跟你谈待遇问题。
还能不能更牛逼?
如果你想薪水拿得高一点,待遇好一点,此时不应该骄傲,你应该大手一挥:且慢,我还可以更牛逼!
上面的宏定义中,我们增加了一个type类型参数,来兼容不同的数据类型,此时此刻,为了薪水,我们应该把这个也省去。如何做到?使用typeof就可以了,typeof是GNU C新增的一个关键字,用来获取数据类型,我们不用传参进去,让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)后,就可以消除这个警告!
此刻,面试官看到你的这个宏,估计会倒吸一口气:乖乖,果然是后生可畏,这家伙比我还牛逼!你等着,HR待会过来跟你谈薪水!
恭喜你,拿到offer了!