- 2.2.2 带参数的宏替换
- define 宏名(参数表)字符串
- define min(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
- define max(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
- include<stdio.h>
- include<stdio.h>
- define print()((void)(3))
- include<stdio.h>
- define min(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
- define min_replace(x,y)({x<y?x:y;})
- define print(……)printf(VA_ARGS)
- include<stdio.h>
- define print(……)printf(VA_ARGS)
- define printf(tem,……)fprintf(stdout,tem,##VA_ARGS)
- include<stdio.h>
- define print(temp,……)fprintf(stdout,temp,##VA_ARGS)
- include<stdio.h>
- define print(temp,……)fprintf(stdout,temp,VA_ARGS)
- include<stdio.h>
- define return_exam(p)if(!(p))\
2.2.2 带参数的宏替换
带参数的宏替换,其定义的一般形式为:
define 宏名(参数表)字符串
在讲解带参数的宏的使用之前,同样先来看看使用带参数的宏时需要注意的几点。
宏名和参数表的括号间不能有空格。
宏替换只做替换,不做计算和表达式求解,这一点要格外注意。
函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存。
宏的哑实结合(哑实结合类似于函数调用过程中实参替代形参的过程)不存在类型,也没有类型转换。
宏展开使源程序变长,而函数调用则不会。
下面通过Linux下的两个典型的宏定义来介绍带参数的宏定义。说其典型,是由于其宏定义的“完整性”,至于“完整性”究竟体现在什么地方,我们通过代码来逐一分析。
define min(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
_x<_y?_x:_y;})
define max(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
_x>_y?_x:_y;})
在上面的两个宏中都有代码“(void)(&_x==&_y);”,可能不少读者对其并不理解,下面进行仔细分析。首先分析“==”,这是一个逻辑表达式的运算符,它要求两边的比较类型必须一致。如果&x和&y的类型不一致,一个为char,另一个为int,那么使用gcc编译就会出现警告信息,用VC++6.0编译时则会报错“error C2446:'==':no conversion from'char'to'int'”。代码“(void)(&_x==&_y);”的功能就相当于执行一个简单的判断操作,判断x和y的类型是否一致。别小看了这句代码,学会使用它会为编码带来不少便捷。下面给出一个小示例。
include<stdio.h>
void print()
{
printf("hello world!\n");
return;
}
void main(int argc,char*argv)
{
print();
return;
}
运行结果:
hello world!
现在适当修改一下上面的代码。
include<stdio.h>
void print()
{
printf("hello world!\n");
return;
}
void main(int argc,char*argv)
{
define print()((void)(3))
print();
return;
}
运行结果没有任何输出。
这次的结果没有了之前的那句“hello world!”,可以看出此时函数并没有被调用,这是因为“#define print()((void)(3))”使之后的调用函数print()成为一个空操作,所以这个函数在接下来的代码中都不会被调用了,就像被“冲刷掉”了一样。看了上面给出的宏,细心的读者会有另外一个疑惑:在“#define min(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);_x<_y?_x:_y;})”中,为什么要使用“typeof(y)_y=(y)”这样的替换,而不直接使用“typeof(x)==typeof(y)”或者“x<y?x:y;”呢?因为使用“typeof(x)==typeof(y)”就像使用“char==int”一样,这是不允许的。如果在宏中没有使用“(void)(&_x==&_y);”这样的语句,那么编译时就相当于失去了类型检测功能。在上面的宏中使用“typeof(y)_y=(y)”这样的转换是为了防止x和y为一个表达式的情况,如x=i++,如果不转换,那么i++就会多执行几次操作,得到的就不是想要的结果。如果使用了“typeof(y)_y=(y)”这样的转换,就不会出现这样的问题了。我们可以通过下面一段代码来看看它们之间的区别。
include<stdio.h>
define min(x,y)({typeof(x)_x=(x);typeof(y)_y=(y);(void)(&_x==&_y);
_x<_y?_x:_y;})
define min_replace(x,y)({x<y?x:y;})
void main()
{
int x=1;
int y=2;
int result=min(x++,y);
printf("没有替换时的运行结果为:%d\n",result);
int x1=1;
int y1=2;
int result1=min_replace(x1++,y1);
printf("替换之后的运行结果为:%d\n",result1);
return;
}
在Linux环境下使用gcc编译的运行结果:
没有替换时的运行结果为:1
替换之后的运行结果为:2
分析上面的运行结果可以发现,使用相同输入的两种宏得到的最终结果并不一样,在2.3节中我们还会对其进行详细分析。
下面来看如何使用宏定义实现变参,先看看实现方法。
define print(……)printf(VA_ARGS)
在这个宏中,“……”指可变参数。可变参数的实现方式就是使用“……”所代表的内容替代VA_ARGS,看看下面的代码。
include<stdio.h>
define print(……)printf(VA_ARGS)
int main(int argc,char*argv)
{
print("hello world——%d\n",1111);
return 0;
}
在Linux环境下采用gcc进行编译的运行结果:
hello world——1111
再看代码:
define printf(tem,……)fprintf(stdout,tem,##VA_ARGS)
可能有些读者对fprintf()函数感觉有些陌生,在此对fprintf()函数进行简单的讲解,其函数原型为:
int printf(FILEstream,charformat[,argument])
这个函数的功能为根据指定的format格式发送消息到stream(流)指定的文件中,在前面的宏中使用stdout表示标准输出,fprintf()的返回值是输出的字符数,发生错误时返回一个负值。
include<stdio.h>
define print(temp,……)fprintf(stdout,temp,##VA_ARGS)
int main(int argc,char*argv)
{
print("hello world——%d\n",1111);
return 0;
}
在Linux环境下采用gcc进行编译的运行结果:
hello world——1111
temp在此处的作用为设定输出字符串的格式,后面的“……”为可变参数。现在问题来了,在宏定义中为什么要使用“##”呢?如果没有使用##,会怎么样呢?看看下面的代码:
include<stdio.h>
define print(temp,……)fprintf(stdout,temp,VA_ARGS)
int main(int argc,char*argv)
{
print("hello world\n");
return 0;
}
在Linux环境下采用gcc进行编译时发生了如下错误:
arg.c:In function'main':
arg.c:7:2:error:expected expression before')'token
为什么会出现上述错误呢?现在我们来分析一下。进行宏替换,“print("hello world\n")”变为“fprintf(stdout,"hello world\n",)”后,会发现后面出现了一个逗号导致发生错误。如果有“##”,就不会出现这样的错误,这是因为可变参数被忽略或为空,“##”操作将使预处理器去除它前面的那个逗号。如果存在可变参数,“##”也能正常工作。
介绍了“##”,再来介绍一下“#”。先来看看下面一段代码。
include<stdio.h>
define return_exam(p)if(!(p))\
{printf("error:"#p"file_name:%s\tfunction_name:%s\tline:%d.\n",\
FILE,func,LINE);return 0;}
int print()
{
return_exam(0);
}
int main(int argc,char*argv)
{
print();
printf("hello world!\n");
return 0;
}
在Linux环境下采用gcc进行编译的运行结果:
error:0 file_name:arg.c function_name:print line:9.
hello world!
因为这里只是为了体现要讲解的宏,所以对代码做了最大的简化,后续章节还将深入讲解如何使用宏来调试代码。“#”的作用就是对其后面的宏参数进行字符串化操作,即在对宏变量进行替换之后在其左右各加上一个双引号,这就使得“"#p"”变为了“""p""”,我们发现这样两边的“""”就消失了。