C/C++ 宏定义 (三)
的类型之外,这些函数都一样。因此,这样定义每一个函数似乎是个很蠢的做法。
解决的办法是定义一个宏,并使它展开后成为max函数的定义。宏会有唯一的参数type,它表示形式参数和返回值的类型。这里还有个问题,如果我们是用宏来创建多个max函数,程序将无法编译。(C语言不允许在同一文件中出现两个同名的函数。)为了解决这个问题,我们是用##运算符为每个版本的max函数构造不同的名字。下面的例子:请注意宏的定义中是如何将type和_max相连来形成新函数名的。假如我们需要一个针对float值的max函数。
#define GENERIC_MAX (type) \
type type##_max(type x, type y) \
{ \
return x > y x :y; \
}
GENERIC_MAX(float)
//预处理器会将这行展开为下面的代码:
float float_max(float x, float y) { return x > y x :y; }
再如:
#define PHP_FUNCTION ZEND_FUNCTION
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \
zval *this_ptr, int return_value_used TSRMLS_DC
PHP_FUNCTION(count);
// 预处理器处理以后, PHP_FUCNTION(count);就展开为如下代码
void zif_count(int ht, zval *return_value, zval **return_value_ptr,
zval *this_ptr, int return_value_used TSRMLS_DC)
宏ZEND_FN(name)中有一个"##",它的作用一如之前所说,是一个连接符,将zif和宏的变量name的值连接起来。 以这种连接的方式以基础,多次使用这种宏形式,可以将它当作一个代码生成器,这样可以在一定程度上减少代码密度, 我们也可以将它理解为一种代码重用的手段,间接地减少不小心所造成的错误。
5. 宏的通用属性
现在我们已经讨论过简单的宏和带参数的宏了,我们来看一下它们都需要遵守的规则。
1) 、宏的替换列表可以包含对另一个宏的调用。例如,我们可以用宏PI来定义宏TWO_PI:
#definePI 3.14159
#defineTWO_PI (2*PI)
当预处理器在后面的程序中遇到TWO_PI时,会将它替换成(2*PI)。接着,预处理器会重新检查替换列表,看它是否包含其他宏的调用(在这个例子中,调用了宏PI)。预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。
2) 、预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏名。例如,假设程序含有如下代码行:
#define SIZE 256
int BUFFER_SIZE;
if (BUFFER_SIZE> SIZE)
puts("Error : SIZEexceeded");
//预处理后,这些代码行会变为:
int BUFFER_SIZE;
if (BUFFER_SIZE> 256)
puts("Error :SIZEexceeded");
标识符BUFFER_ZISE和字符串"Error:SIZE exceeded"没有被预处理影响,虽然它们都包含SIZE。
3) 、一个宏定义的作用范围通常到出现这个宏的文件末尾。由于宏是由预处理器处理的,他们不遵从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。
4) 、宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异是允许的,但是宏的替换列表(和参数,如果有的话)中的记号都必须一致。
5) 、宏可以使用#undef指令“取消定义”。#undef指令有如下形式:
[#undef指令] #undef 标识符
其中标识符是一个宏名。例如,指令
#undef N
会删除宏N当前的定义。(如果N没有被定义成一个宏,#undef指令没有任何作用。)#undef指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。
6. 宏定义中圆括号
在我们前面定义的宏的替换列表中有大量的圆括号。确实需要它们吗?答案是绝对需要。如果我们少用几个圆括号,宏可能有时会得到意料之外的——而且是不希望有的结果。对于在一个宏定义中哪里要加圆括号有两条规则要遵守:
首先,如果宏的替换列表中有运算符,那么始终要将替换列表放在括号中:
#define TWO_PI (2*3.14159)
其次,如果宏有参数,每次参数在替换列表中出现时都要放在圆括号中:
#define SCALE(x) ((x)*10)
没有括号的话,我们将无法确保编译器会将替换列表和参数作为完整的表达式。编译器可能会不按我们期望的方式应用运算符的优先级和结合性规则。
为了展示为替换列表添加圆括号的重要性,考虑下面的宏定义,其中的替换列表没有添加圆括号:
#define TWO_PI 2*3.14159
/* 需要给替换列表加圆括号 */
在预处理时,语句
conversion_factor = 360/TWO_PI;
//变为
conversion_factor = 360/2*3.14159;
除法会在乘法之前执行,产生的结果并不是期望的结果。
当宏有参数时,仅给替换列表添加圆括号是不够的。参数的每一次出现都要添加圆括号。例如,假设SCALE定义如下:
#define SCALE(x) (x*10) /* 需要给x添加括号 */
在预处理过程中,语句
j = SCALE(i+1);
变为
j = (i+1*10);
由于乘法的优先级比加法高,这条语句等价于
j = i+10;
当然,我们希望的是
j = (i+1)*10;
在宏定义中缺少圆括号会导致C语言中最让人讨厌的错误。程序通常仍然可以编