前言
最近在写有关日志的demo,自己造了轮子,感觉自己实现的不是很好,实现的是一个定参数的日志输出,包括时间、日志等级、进程id、源文件名等必要的信息,但这种固定的格式扩展性不是很好,又了解到一些不定参数的使用,今天写一下文章记录一下。
C语言风格不定参数使用
基本格式
宏定义格式
#define LOG(fmt, ...)printf("/*指定输出格式*/" fmt,/*指定打印参数*/,__FILE__,__LINE__,##__VA_ARGS__)
下面让我来解释一下:
fmt
是宏的第一个参数,表示格式化字符串(format string),就像 printf("Hello %s", name)
中的 "Hello %s"
。
C语言中不定参数使用...来代替,传递的参数会传递给printf处理。
__FILE__,__LINE__都是预处理部宏,表示源文件名和行号,如果不想输出可以不加
__VA_ARGS__是一个预处理宏,用于处理可变参数,前面的##是为了处理没有传递多参数时多余的逗号,例如如果只传递一个参数,调用就会被替换成下面这种格式
//调用
LOG("hello");
//不加##上面的调用会被替换成
printf("[%s:%d]hello\n", __FILE__, __LINE__, );
//__LINE__的后面多了一个逗号,编译器就会报错,加了##后逗号就可以被正确处理了
必要的头文件
只需要包含下面这个头文件即可
#include <stdarg.h>
现在我们比编写一个函数来试验一下
这个函数可以接受多个参数,count代表打印多少个数据,后面又count个要打印的数,PrintNum(5,1,2,3,4,5)的调用结果为
argument[0]:1
argument[1]:2
argument[2]:3
argument[3]:4
argument[4]:5
#define _GNU_SOURCE是glibc中的一个宏定义,如果要使用va_list不仅要包含头文件还需要包含此宏定义
va_list是一个可变参数列表,他提供了一些方法访问未指定数量和类型的参数
最后使用完一定要调用va_end将定义的可变参数列表指针ap指向的资源释放,保持一个良好的编程习惯
#define _GNU_SOURCE
void PrintNum(int count,...)
{
va_list ap;
//第二个参数传可变参数列表前的最后一个参数
va_start(ap,count);
for(int i=0;i<count;i++)
{
int num=va_arg(ap,int);//修改ap指针来返回下一个参数
printf("argument[%d]:%d\n",i,num);
}
va_end(ap);//将ap指针置为空
}
C++风格不定参数使用:
C++11引入了变参模板(template<typename... Args>
),使得我们能够创建接受不定个数模板参数的模板。
基本语法
template<typename T,typename ...Args>
T代表一个任意类型的参数,...Args代表可变参数
下面我们使用这个模板来实现C++的可变参数
void Printf()
{
std::cout<<std::endl;
}
template<typename T,typename ...Args>
void Printf(const T& v,Args && ...args)
{
std::cout<<v;
if(sizeof ...(args)>0)
{
Printf(std::forward<Args>(args)...);
}
else
{
Printf();
}
}
Printf("你好");
Printf("你好","你好");
Printf("你好","你好",789);
这个函数接收任意类型的参数和一个右值引用的可变参数包,使用递归来输出可变参数,由于编译器参数推导到最后是没有参数的,我们还需要自定义一个无参函数来确保程序能够正确运行
上面这段代码会输出下面这些内容
你好
你好你好
你好你好789
如果你编写代码的c++版本更高(c++17),你可以使用下面这段代码简化操作
template<typename... Args>
void Printf(Args&&... args) {
(std::cout << ... << args) << std::endl;
}
如果觉得文章写的还不错的话,请点个赞吧,如果有写的不对的地方,还请指正