有关 va_list 的一些笔记

写点轻松点儿的内容~

va_list是 C 语言专门用来解决可变参数问题的工具,于 C99 标准引入,尽管很早从一些书上阅读过有关的内容,但是一直没有整理,这次整理下。

如何使用

由于va_list是 C 语言标准提供的接口,所以支持 C99 标准的 C 编译器都支持相关功能。使用时,需要先引入头文件:

1
#include <stdarg.h>

这个头文件里声明了一个类型va_list和针对这个类型的四个常用宏:va_startva_argva_endva_copy

va_start

原型:

1
void va_start(va_list ap, last);

功能:初始化一个va_list类型的变量ap
参数:ap是一个va_list类型的变量,实际是一个指针,指向可变参数列表的第一个参数;last是参数列表之前的最后一个参数,被调用函数一定知道这个参数的类型,且这个参数不能声明为register,也不能是一个函数或数组。同时,这个参数还可以用于向函数传递一些信息,比如元素的个数,格式化字符串等。

va_arg

原型:

1
type va_arg(va_list ap, type);

功能:从已初始化的va_list变量中取出下一个参数,并返回其值,同时ap指针向后移动,指向下一个参数。
参数:ap是一个已初始化的va_list变量;type是类型名,表示期望取出的参数的数据类型,如intdouble等。

va_end

1
void va_end(va_list ap);

功能:结束对可变参数列表的遍历,并执行必要的清理工作。
参数:ap是需要清理的va_list变量。
说明:每一个va_start都必须与对应的va_end匹配,在调用完va_end后,ap就是未定义的了,无法直接再次使用。

va_copy

1
void va_copy(va_list dest, va_list src);

功能:拷贝srcdest中。
参数:dest是目标,作为src的副本;src是源,被拷贝。
说明:在需要多次遍历同一个可变参数列表时,可以先用va_copy创建一个副本。另外,使用va_copy初始化的va_list,最后也需要使用va_end进行清理。

test case

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
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <stdio.h>
#include <stdarg.h>

int myPrintf(const char *fmt, ...) {
int cnt = 0;
va_list ap;
int c;
char *str;
va_start(ap, fmt);
while(*fmt) {
switch(*fmt++) {
case 'c':
c = va_arg(ap, int);
putchar(c);
putchar('\n');
++cnt;
break;
case 's':
str = va_arg(ap, char*);
puts(str);
++cnt;
break;
default:
break;
}
}
va_end(ap);
return cnt;
}

double doubleAverage(int cnt, ...) {
double sum = 0.0;
double d = 0.0;
va_list ap;
va_start(ap, cnt);
for(int i = 0; i < cnt; ++i) {
d = va_arg(ap, double);
sum += d;
}
va_end(ap);
return sum / cnt;
}

int main() {
myPrintf("%c%s%c%s", 'x', "ddd", 'x', "sss");
printf("%lf\n", doubleAverage(5, 1.0, 2.0, 3.0, 4.0, 5.0));
return 0;
}
/*
x
ddd
x
sss
3.000000
*/

在上述代码中,使用可变参数完成了两个简单的函数,这两个函数有一个很关键的共同点,那就是提供给这两个函数的第一个参数都是由用户传入的,而这个参数决定着被调函数何时执行结束。那么换句话说,使用可变参数实现的函数的结束时间,是由用户来掌控的,这样的行为也与库函数printfscanf也是一致的。


Buy me a coffee ? :)
0%