1.如何查看宏展开
在开始之前,先简单的介绍下,我们自己定义的一个宏,在引用的代码中是如何被展开的。我用的是Microsoft Visual C++ 6.0,project menu -> settings,C/C++选项卡,在Project Options加上/P,如图 1 1所示。rebuild all,会产生与.cpp同名的.i文件。打开.i文件,在这里就可以看到代码中宏是如何被展开的。
图 1 1 添加“/P”编译选项
比如:
#define CONS(a,b) int(a##e##b)
int n = CONS(2, 3); // 被展开为int n = int(2e3);即n = 2000;
2.预处理操作符
以前,我经常看到一些巧妙的宏定义,里面有用到#、##、等符号,但不懂这些#是干么用的,今天突然想起这事,决定把它们彻底搞明白。
打开MSDN查看相关说明,如下表格所示,注意这些预处理操作符只能用在#define的宏定义中 。
Preprocessor Operators | Action |
Stringizing operator (#) | 字符串化操作符,给对应的实参加上双引号。 |
Charizing operator (#@) | 字符化操作符,给对应的实参加上单引号。 |
Token-pasting operator (##) | 符号连接操作符,串联两个符号。 |
2.1. Stringizing operator (#)
字符串化操作符,作用:将宏定义中的传入参数名转换成用一对双引号括起来,即字符串化。我们测试看看(示例来自MSDN)。
#define stringer( x ) printf( #x "/n" )
void main()
{
stringer( In quotes in the printf function call/n );
stringer( "In quotes when printed to the screen"/n );
stringer( "This: /" prints an escaped double quote" );
}
用上面介绍的方法查看宏展开(在.i文件中),以上代码预编译时展开成如下代码,可以发现用字符串化(Stringizing)操作时,如果实参中有引号、反斜线字符时,宏展开时会在这些符号钱自动加上“/”以便将这些字符也字符串化。
void main()
{
printf( "In quotes in the printf function call/n" "/n" );
printf( "/"In quotes when printed to the screen/"/n" "/n" );
printf( "/"This: ///" prints an escaped double quote/"" "/n" );
}
最终输入如下:
In quotes in the printf function call
"In quotes when printed to the screen"
"This: /" prints an escaped double quote"
注意:
◆转义字符
◇某些形式的传入参数名中,若存在特殊字符,编译器会自动为其添加转义字符号'/'。
◆对空格的处理
◇忽略传入参数名前面和后面的空格。
如:stringer( ABC ); // 宏展开为printf( "ABC" "/n" );
◇当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格。
如:stringer( ABC DEF ); // 宏展开为printf( "ABC DEF" "/n" );
2.2. Charizing operator (#@)
字符化操作符,作用:将宏定义中的传入参数名转换成用一对单引号括起来,即字符化。如:
#define MAKECHAR(ch) #@ch
char a = MAKECHAR (b); // 被宏展开为char a = 'b';
注意:
在默认的类型转换中,传入的参数如果不是字符型,会被截断成char型。
如:
char a = MAKECHAR (abcd); 将会截断成 a='d'。
2.3. Token-pasting operator (##)
符号连接操作符,作用:将两个符号串联成一个符号使用。Token-Pasting Operator也成为merging operator,即合并操作。TEXT这个宏我想搞Windows编程的人都不陌生,我们看看它的定义:
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
用实际例子看看怎么个连接法,在UNICODE环境中:
TEXT(ABC); // 宏展开为LABC;
TEXT("ABC"); // 宏展开为L"ABC";
再比如:
#define CONS(a,b) int(a##e##b)
int n = CONS(2, 3); // 被展开为int n = int(2e3);即n = 2000;
3.宏嵌套
宏定义是允许嵌套的,但要注意:宏定义里有用’#'、’#@’、'##'的地方宏参数是不会再展开 。为了说明问题,让我们来看看一个简单例子。
#define MAX_VAL 100
#define _STR(arg) #arg
#define STR(arg) _STR(arg)
int main()
{
puts(_STR(MAX_VAL));
puts(STR(MAX_VAL));
return 0 ;
}
先看看.i文件中的宏展开是如何的:
int main()
{
puts("MAX_VAL");
puts("100");
return 0 ;
}
输出:
MAX_VAL
100
从输出结果就可以看出其差别所在,之所以会这样,就是因为:宏定义里有用’#'、’#@’、'##'的地方宏参数是不会再展开 。然而解决这个问题的方法很简单,加多一层中间转换宏,中间转换宏的作用就是对宏参数进行宏展开。在许多大型项目、MFC代码、微软代码的头文件中这样的定义随处可见。就像上面介绍的TEXT宏嵌套定义亦是如此。
4.总结
宏的设计很巧妙,只要你能想得出,能大大的强化你的代码,在学习和工作的过程中,要善于总结、归纳、收藏巧妙的宏应用。仅供参考,如有误,请赐教。如果我写的东西对你有用,不要忘了顶下。