10.2.3 带参宏
还可以定义像函数的宏,即带参数的宏。例如:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
定义了宏 MAX,它的作用是在两个参数中找到相对较大的那个。宏 MAX 带了两个参数 a 和 b。宏值部分“((a) >(b) ?(a) :(b))”是一个表达式,通过三目运算符来获取并返回参数 a 和 b 中相对较大的那一个。
下面在主函数中使用宏 MAX。例如:
在 printf 函数中使用了宏 MAX,并将其两个参数分别设置为整型常量值 10 和 20。宏 MAX 的功能就是在这两个参数中寻找并返回相对较大的那一个。
对源代码进行预处理后会发现,经宏替换之后,“MAX(10, 20)”被替换成了:
((10) > (20) ? (10) : (20))
即先将宏值部分的参数 a 替换成 10,参数 b 替换成 20,然后再将宏值部分替换至使用宏 MAX 的地方。
编译运行程序,结果如下:
MAX:20
虽然带参宏的定义和使用方式与函数非常相似,但带参宏和函数还是有很大区别的。首先,函数需要进行编译,而宏是由预处理器来处理,在进行代码编译阶段,宏已经不存在了;其次,函数有函数体和返回值类型,而带参宏只有对应的宏值;第三,函数在调用时,会对参数进行求值,而带参宏只是对参数的简单替换,不会对参数进行求值。因此,在宏定义时,宏值部分的每个参数都加上了小括号,这是一个好习惯。假设需要一个用于计算平方的宏 SQUARE,其定义如下:
#define SQUARE(n) (n * n)
在主函数中,这样来使用宏 SQUARE:
printf("Result:%d\n", SQUARE(1 + 2));
即想计算并打印输出 3(1 与 2 的和)的平方,但我们编译运行程序,会发现结果如下:
Result:5
打印的结果并非所预想的 9,而是 5。这是为何?看一下预处理后的内容就知道了。
对源代码进行预处理后,会发现宏 SQUARE 被替换为:
(1 + 2 * 1 + 2)
因此将宏 SQUARE 的定义修改如下:
#define SQUARE(n) ((n) * (n))
再对源代码进行预处理,发现宏 SQUARE 被替换为:
((1 + 2) * (1 + 2))
重新编译运行程序,结果如下:
Result:9
最后再讲两个特殊和有趣的符号:“#”和“##”,称其特殊,是因为它们在带参宏中具有特别的功能,说其有趣,是通过对它们的灵活使用,能让代码展现出神奇、灵动的一面。
1.将参数转换为字符串常量
在带参宏的定义中,可以使用“#”来将参数转换为字符串常量。例如:
#define STR(s) #s
在宏 STR 的定义中,在参数 s 的前面加上“#”作为宏值部分,这样就可以达到将参数 s 转换为字符串常量的功能。例如:
在 printf 函数中使用了宏 STR,并将一个整型常量表达式作为参数,宏 STR 的功能就是将这个整型常量表达式转换为一个字符串常量。
对源代码进行预处理,进行宏替换之后,printf 语句已经变为:
printf("Result:%s\n", "1 + 2");
可见,原先的整型常量表达式“1 + 2”已经变成了字符串常量"1 + 2"。
2.参数结合
我们还可以使用“##”来对带参宏中的参数进行结合。例如:
#define COMB(a, b) a##b
定义了带参宏 COMB,它有两个参数 a 和 b。该宏的功能是通过“##”符号将两个参数结合在一起,组成一个新的字符序列(并非字符串)。
下面来使用这个宏。例如:
printf("Result: %d\n", COMB(10, 20));
这是将整型常量 10 和 20 作为参数来使用宏 COMB,并将结果以整型格式打印输出到控制台窗口。对宏 COMB 预处理后的结果为:
printf("Result: %d\n", 1020);
宏 COMB 将参数 10 和 20 结合在一起,组成 1020,printf 函数会将其作为一个整型常量来进行打印输出。
编译运行程序,结果如下:
Result: 1020
3.宏的嵌套使用
也可以将一个宏作为另一个宏的参数,进行宏的嵌套使用。例如:
程序代码中,首先定义了宏 NUM,其宏值为 10;然后定义了带参宏 ADD,其功能是将其参数值加上 5。在主函数的 printf 函数中使用了宏 ADD,并将宏 NUM 作为其参数。
对程序代码进行预处理,预处理后的 printf 函数语句会变成:
printf("Result: %d\n", ((10) + 5));
可见,宏 ADD 会被替换为“((10) + 5)”,而宏 NUM 会被替换为 10。即带参宏中的参数本身又是一个宏时,预处理器会对这个参数宏先进行替换处理。也就是宏在嵌套使用时,会从内到外依次替换作为参数的宏。
但有一种特殊情况是例外,就是当带参宏的宏值部分含有“#”或“##”符号时,则作为参数的宏是不会被展开的。例如:
程序代码中定义了带参宏 STR,它的功能为将参数转换为字符串常量。在主函数的 printf 函数中,在使用带参宏 STR 时,是将宏 NUM 作为其参数的。
预处理之后的 printf 函数调用语句如下:
printf("Result: %s\n", "NUM");
预处理之后的 STR 宏部分,并非是"10",而被替换为"NUM",也就是作为参数的宏 NUM 并未被替换。导致参数宏未被替换的原因,就是在宏 STR 的定义中使用了“#”符号。
那如何解决这个问题呢?
4.转换宏
可以使用一个不包含“#”和“##”符号的转换宏,例如:
代码中又定义了一个带参宏 TOSTR,它有一个参数 n,宏 TOSTR 的功能非常简单,就是继续将 n 作为参数来使用宏 STR。在 printf 函数调用语句中,将原来的宏 STR 改为使用宏 TOSTR,参数仍为宏 NUM。现在再看一下预处理之后的 printf 函数调用语句:
printf("Result: %s\n", "10");
从结果可见,作为参数的宏 NUM 被替换成 10 了。这是因为在宏 TOSTR 中并没有使用“#”或“##”符号,因此,作为参数的宏 NUM 会被进行替换处理,然后将替换处理后的结果作为参数再来使用宏 STR,将其转换为对应的字符串常量。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论