一些函数(如printf)接受数量可变的参数。stdvar.h头文件提供了工具,让用户自定义带可变参数的函数。通过把宏参数列表中最后的参数写成省略号(即3个点...)来实现这一功能。这样,预定义宏__VA_ARGS__可用在替换部分中,表明省略号代表什么。举个例子:
#define LOG(...) printf(__VA_ARGS__)
int main()
{
LOG("hello\n"); // __VA_ARGS__== "hello\n"
LOG("%s\n", "hello"); // __VA_ARGS__== "%s\n", "hello"
}
上面的例子其实就相当于将LOG替换成printf,但实际上可以增加一些额外的信息,在程序中让打印信息更全。接下来介绍几种简单打印实现,并介绍可变参数
1、纯使用宏的实现
#define print_out(format, ...) \
{ \
time_t t = time(NULL); \
struct tm ttt = *localtime(&t);\
fprintf(stdout, "["__FILE__"][%4d-%02d-%02d %02d:%02d:%02d][%s:%d] " format "\n", \
ttt.tm_year + 1900, ttt.tm_mon + 1, ttt.tm_mday, ttt.tm_hour, \
ttt.tm_min, ttt.tm_sec, __FUNCTION__ , __LINE__, ##__VA_ARGS__); \
}
这个在之前的博客中已经写过了,但是这里详细的介绍一下。这是一个包含时间,文件名,函数名,行号的打印宏。其中,各个宏的含义如下
__FILE__:表示当前源代码文件名的字符串字面量(可以直接使用字符串拼接,如上例所示)
__FUNCTION__:函数名
__LINE__:行号
##__VA_ARGS__:__VA_ARGS__的函数介绍过了,宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错
举个例子,
#include <stdio.h>
#include <stdarg.h>
#define LOG(format, ...) do{printf(format, __VA_ARGS__);}while(0)
int main()
{
LOG("hello\n");
}
这样使用gcc编译会失败,但是使用vs2013编译好像是没问题的。但是如果加上##,gcc编译就可以成功。
2、使用宏和函数的实现
#include<stdio.h>
#include <stdarg.h>
#define LOG_INFO(...) \
log_info(__FILE__, __FUNCTION__, __LINE__, \
__VA_ARGS__)
void log_info(const char *file, const char *func, long line,
const char *format, ...)
{
va_list args;//声明一个存储参数的对象
char buf[2048] = { 0 };
va_start(args, format); //把args初始化为参数列表
vsprintf(buf, format, args);
printf("[%s][%s:%d] " "%s", file, func, line, buf);
va_end(args); //清理工作
}
在该例子中,使用了可变参数。使用变参宏,用法相对比较复杂,必须按如下步骤进行:
- 提供一个使用省略号的函数原型;
- 在函数定义中创建一个va_list类型的变量;
- 用宏把该变量初始化为一个参数列表;
- 用宏访问参数列表;
- 用宏完成清理工作。
当然,也可以使用纯函数的,或者也可以将标准输出流改为文件流,向指定的文件输出打印信息,还可以按照自己的需求增加其他的内容。