本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表
所谓可变参数,就是一个不限定参数数量的函数,我们可以往里面传入任意个数的参数,以达成某些目的。
关联:C++11可变模板参数
1.函数
1 2 3 4 5 6
| #include <stdarg.h>
void va_start(va_list ap, last); type va_arg(va_list ap, type); void va_end(va_list ap); void va_copy(va_list dest, va_list src);
|
1.1 va_start
1
| void va_start(va_list ap, last_arg);
|
- ap: 这是一个 va_list 类型的对象,它用来存储通过 va_arg 获取额外参数时所必需的信息
- 这个函数的作用是初始化
ap
变量,它与 va_arg 和 va_end 函数一起使用。
- last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数。
要想设置一个带可变参数的函数,函数声明是下面这样的
1
| void test(int a,int b, ...);
|
这里出现的省略号就是可变参数的特征,而变量b就是va_start函数需要的last_arg
1.2 va_arg
1
| type va_arg(va_list ap, type);
|
这个函数的作用是来提取可变参数列表中的参数。注意,每次提取的参数是直接返回的,并没有放到变量ap中。
每次对va_arg的调用都会修改ap,以便下次调用时,返回下一个参数;推断参数的时候需要指定type,如果当前参数类型和type不统一,就会发生不可预知的错误(man手册里面说的)
1
| If ap is passed to a function that uses va_arg(ap,type) then the value of ap is undefined after the return of that function.
|
如果ap被传递给va_arg(ap,type)
,则在该函数返回后,ap的值未定义。
1.3 va_end
1
| void va_end(va_list ap);
|
每一个va_start
都需要有一个配套的va_end
,其用于清空ap
可以把他俩的关系理解为malloc/free
,记得加上就行
1.4 va_copy
这个函数的作用是将可变参数列表从第二个参数src
拷贝到第一个参数dest
1
| void va_copy(va_list dest,va_list src);
|
其也能够初始化dest
。调用了va_copy
后,无须调用va_start
初始化dest
,但va_end
还是需要的。
2.简单示例
2.1 打印多个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include<stdarg.h> #include<stdio.h> int print(int num_args,...) { va_list ap; va_start(ap,num_args); for(int i=0;i<num_args;i++) { printf("%d ",va_arg(ap,int)); } printf("\n"); va_end(ap); }
int main() { print(5,1,2,3,4,5,6,7,8,9); return 0; }
|
运行该函数,会打印如下结果
这就表明了,...
省略号之前的参数,和va_arg
返回可变参数其实是没有关系的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int print(int num_args,...) { va_list ap; va_start(ap,num_args); for(int i=0;i<8;i++) { printf("%d ",va_arg(ap,int)); } printf("\n"); va_end(ap); }
int main() { print(5,1,2,3,4,5,6,7,8,9,10); return 0; }
|
即便在最后都没有使用num_args
,也不会影响结果的正确性。va_start
需要这个参数,其实是用来标识可变参数的起点。
1 2
| $ ./test 1 2 3 4 5 6 7 8
|
2.2 多参数求和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<stdarg.h> #include<stdio.h>
int sum(int num_args, ...) { int val = 0; va_list ap; int i;
va_start(ap, num_args); for(i = 0; i < num_args; i++) { val += va_arg(ap, int); } va_end(ap); return val; }
void test1() { printf("10、20 和 30 的和 = %d\n", sum(3, 10, 20, 30) ); printf("4、20、25 和 30 的和 = %d\n", sum(4, 4, 20, 25, 30) ); }
|
运行如下
1 2 3
| $ ./test 10、20 和 30 的和 = 60 4、20、25 和 30 的和 = 79
|
3.利用可变参数实现log类
现在有了可变参数,我们就可以接用这个参数来进行日志的打印了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #pragma once
#include <cstdio> #include <ctime> #include <cstdarg> #include <cassert> #include <cstring> #include <cerrno> #include <stdlib.h>
#define DEBUG 0 #define NOTICE 1 #define WARINING 2 #define FATAL 3
const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};
void logging(int level, const char *format, ...) { assert(level >= DEBUG || level <= FATAL);
char *name = getenv("USER");
char logInfo[1024]; va_list ap; va_start(ap, format);
vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);
va_end(ap);
FILE *out = (level == FATAL) ? stderr:stdout; fprintf(out, "%s | %u | %s | %s\n", \ log_level[level], \ (unsigned int)time(nullptr),\ name == nullptr ? "unknow":name,\ logInfo); }
|
3.1 vsnprint
作用:使用vsnprintf()
用于向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度。
此函数需要C99或者C++11及以上版本才能支持。
1
| int vsnprintf(char* sbuf, size_t n, const char* format, va_list arg);
|
- 第一个参数:目标缓冲区(字符数组)
- 第二个参数,限定最多打印到缓冲区的字符数量为
n-1
个(留位置给\0
) - 第三个参数,打印的格式(如
%d:%s
) - 第四个参数,可变参数arg,需要用
va_start
初始化
返回:成功打印到sbuf中的字符的个数,不包括末尾追加的\0。如果格式化解析失败,则返回负数。
用这个函数,就能把我们的来源字符串给输入到缓冲区char logInfo[1024];
中
3.2 fprintf
使用fprintf
,将printf的输出打印到指定文件中;用法和printf
是一样的
1
| int fprintf(FILE *stream, const char *format, ...);
|
这样是为了区分stderr/stdout
。同时添加上执行命令的用户信息,以及当前的时间戳
1 2 3 4 5
| fprintf(out, "%s | %u | %s | %s\n", \ log_level[level], \ (unsigned int)time(nullptr),\ name == nullptr ? "unknow":name,\ logInfo);
|
3.3 运行结果
1 2 3 4 5 6
| int main() { logging(DEBUG, "socket create success: %d", 114514); logging(FATAL, "socket:%s:%d", strerror(errno), 11234); return 0; }
|
1 2 3
| $ ./test DEBUG | 1675322313 | muxue | socket create success: 114514 FATAL | 1675322313 | muxue | socket:Success:11234
|
The end
对于可变参数的简单介绍就到这里!基本的使用能看懂久OK啦!