C/C++ 宏定义 (四)

2014-11-23 23:18:22 · 作者: · 浏览: 22
译通过,而且宏似乎也可以工作,仅在少数情况下会出错。
7. 创建较长的宏
1. 较长的宏中的逗号运算符
在创建较长的宏时,逗号运算符会十分有用。特别是可以使用逗号运算符来使替换列表包含一系列表达式。例如,下面的宏会读入一个字符串,再把字符串显示出来:
#define ECHO(s) (get(s), puts(s))
gets函数和puts函数的调用都是表达式,因此使用逗号运算符连接它们是合法的。我们甚至可以把ECHO宏当作一个函数来使用:
ECHO(str); /* 替换为 (gets(str), puts(str)); */
除了使用逗号运算符,我们也许还可以将gets函数和puts函数的调用放在大括号中形成复合语句:
#define ECHO(s) { gets(s); puts(s); }
遗憾的是,这种方式并不奏效。假如我们将ECHO宏用于下面的if语句:
if (echo_flag)
ECHO(str);
else
gets(str);
//将ECHO宏替换会得到下面的结果:
if (echo_flag)
{ gets(str); puts(str); };
else
gets(str);
编译器会将头两行作为完整的if语句:
if (echo_flag)
{ gets(str); puts(str); }
编译器会将跟在后面的分号作为空语句,并且对else子句产生出错信息,因为它不属于任何if语句。我们可以通过记住永远不要在ECHO宏后面加分号来解决这个问题。但是这样做会使程序看起来有些怪异。逗号运算符可以解决ECHO宏的问题,但并不能解决所有宏的问题。假如一个宏需要包含一系列的语句,而不仅仅是一系列的表达式,这时逗号运算符就起不到帮助的作用了。因为它只能连接表达式,不能连接语句。解决的方法是将语句放在do循环中,并将条件设置为假:
2. 宏定义中的do-while循环do
do循环必须始终随跟着一个分号,因此我们不会遇到在if语句中使用宏那样的问题了。为了看到这个技巧(嗯,应该说是技术)的实际作用,让我们将它用于ECHO宏中:
#define ECHO(s) \
do{ \
gets (s) ; \
puts (s) ; \
} while (0)
当使用ECHO宏时,一定要加分号:
ECHO(str);
/* becomes do { gets(str); puts(str); } while (0); */
为什么在宏定义时需要使用do-while语句呢 我们知道do-while循环语句是先执行循环体再判断条件是否成立, 所以说至少会执行一次。当使用do{ }while(0)时由于条件肯定为false,代码也肯定只
执行一次, 肯定只执行一次的代码为什么要放在do-while语句里呢 这种方式适用于宏定义中存在多语句的情况。 如下所示代码:
#define TEST(a, b) a++;b++;
if (expr)
TEST(a, b);
else
do_else();
代码进行预处理后,会变成:
if (expr)
a++;b++;
else
do_else();
这样if-else的结构就被破坏了if后面有两个语句,这样是无法编译通过的,那为什么非要do-while而不是简单的用{}括起来呢。 这样也能保证if后面只有一个语句。例如上面的例子,在调用宏TEST的时候后面加了一个分号, 虽然这个分号可有可无, 但是出于习惯我们一般都会写上。 那如果是把宏里的代码用{}括起来,加上最后的那个分号。 还是不能通过编译。 所以一般的多表达式宏定义中都采用do-while(0)的方式。
3. "空操作"的定义
了解了do-while循环在宏中的作用,再来看"空操作"的定义。在PHP 源码中,由于 PHP需要考虑到平台的移植性和不同的 系统配置, 所以需要在某些时候把一些宏的操作定义为空操作。例如在sapi\thttpd\thttpd.c
文件中的VEC_FREE():
#ifdef SERIALIZE_HEADERS
# define VEC_FREE() smart_str_free(&vec_str)
#else
# define VEC_FREE() do {} while (0)
#endif
这里涉及到条件编译,在定义了SERIALIZE_HEADERS宏的时候将VEC_FREE()定义为如上的内容,而没有定义时, 不需要做任何操作,所以后面的宏将VEC_FREE()定义为一个空操作,不做任何操作,通
常这样来保证一致性, 或者充分利用系统提供的功能。
有时也会使用如下的方式来定义“空操作”,这里的空操作和上面的还是不一样,例如很常见的Debug日志打印宏:
#ifdef DEBUG
# define LOG_MSG printf
#else
# define LOG_MSG(...)
#endif
在编译时如果定义了DEBUG则将LOG_MSG当做printf使用,而不需要调试,正式发布时则将LOG_MSG()宏定义为空, 由于宏是在预编译阶段进行处理的,所以上面的宏相当于从代码中删除了。
上面提到了两种将宏定义为空的定义方式,看上去一样,实际上只要明白了宏都只是简单的代码替换就知道该如何选择了。
8. 预定义宏
C语言中预定义了一些有用的宏,见表预定义宏。这些宏主要是提供当前编译的信息。宏__LINE__和__STDC__是整型常量,其他3个宏是字符串字面量。
表预定义宏:
__LINE__ 被编译的文件的行数
__FILE__ 被编译的文件的名字
__DATE__ 编译的日期(格式"Mmm dd yyyy")
__TIME__ 编译的时间(格式"hh:mm:ss")
__STDC__ 如果编译器接受标准C,那么值为1
1)、 __DATE__宏和__TIME__宏指明程序编译的时间。例如,假设程序以下面的语句开始:
printf("Wacky Windows (c) 1996 Wacky Software, Inc.\n");
printf("Compiled on %s at %s\n", __DATE__,__TIME__);
每次程序开始执行,程序都会显示下面两行:
Wacky Windows (c) 1996 Wacky Software, Inc.
Compiled on Dec 23 1996 at 22:18:48
这样的信息可以帮助区分同一个程序的不同版本。
2)、我