本期话题:
今天,我们聊一道C语言关于宏定义的笔试题:写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
聊一聊:
这道题考察的是 C 语言宏定义的知识。我们很容易会想到下边的答案:
#define MIN(a,b) ((a) < (b) ? (a) : (b))
宏定义会在编译的时候进行替换展开,最好将宏中的参数用括号括起来。这样就避免了当一个表达式同时含有宏定义和其他高优先级运算符时,破坏整个表达式的运算顺序 。上边的答案解决了一些问题,但会不会存在其他漏洞呢?如果我们用这个宏进行比较:
least = MIN(i++, j++);
替换之后
least = ((i++) < (j++) ? (i++) : (j++));
无论 i 或者 j 谁小,都会做两次自增运算,导致出现 i 和 j 完成比较后出现错误。
如何消除这种参数变化引起的副作用呢?答案是用语句表达式来定义这个宏。
注意,语句表达式是 GNU C 对 C语言标准的扩展,允许在一个表达式里内嵌语句。
语句表达式最外面使用小括号()括起来,里面一对大括号 {} 包起来的是代码块,代码块里允许内嵌各种语句。
在语句表达式中,用两个临时变量暂时存储这两个参数。
#define MIN_t(x, y) ({ \
int _x = x; \
int _y = y; \
_x < _y ? _x : _y; \
})
在语句表达式中,定义了两个局部变量 _x、_y 来存储宏参数 x 和 y 的值,然后用 _x、_y 比较大小。这样就避免了两次自增运算引起的问题。然而,上边的宏定义中,也存在不足,你知道是什么吗?那就是,临时变量的数据类型为 int 型。也就是说,这个宏定义只能比较两个整型数据。如何完善这个宏定义,使其能够支持任意类型的数据比较大小呢?当然,我们可以这样写:
#define MIN_t(type, x, y) ({ \
type _x = x; \
type _y = y; \
_x < _y ? _x : _y; \
})
但是,这个宏就有三个参数了,每次比较数据大小,还需要将参数类型 type 传进去。别着急,肯定有办法弥补这个不足。也就是,想办法获取比较数据的类型。对于 GNU C 来说,没问题。因为它扩展了一个关键字 typeof,可以获取数据类型。对于其他版本的 C 语言,需要查看手册进行考证,在此不展开介绍。最终的优化结果如下:
#define MIN(x,y) {
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void)(&_x = &_y); \
_x < _y ? _x : _y; \
})
这个答案比较完美了。
其中 (void)(&x==&y) 是用于检查 x 和 y 的类型是否相同。它有两个作用:
一是用来给用户提示一个警告。对于不同类型的指针比较,编译器会发出一个警告,提示两种数据的类型不同。
二是两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个 warning,加一个(void)后,就可以消除这个警告。
至此,整个宏定义面试题的各种解法就分析完毕了。看完之后,你是否有所收获呢?