文章结构:


不定参数函数形式

在标准库中,某些函数的参数数量是不固定,是可变的,例如print()等。

不定参数的形式一般为:

type function_name(type arg1, [type arg_n], ...) {
    va_list list_args;
    ... ...

}

在函数的参数中,第一个参数是一定要声明确切的类型及参数名的,之后的参数可以声明类型及参数名,也可以直接用...来代替表示不定参数。

函数体在执行过程中需要知道...所表示的不定参数的具体类型,所以如系统的print()函数第一个参数是char*并用%[flag]中的flag来告诉函数第n个参数的具体类型。即,要嘛所有参数固定为已知类型,如求平均值时都固定为float;要嘛在类型不固定的情况下需要由arg1arg_n来告诉函数后续...的具体参数类型。

在不定参数函数体的开头,都要声明一个va_list的对象以便后续被下面的宏定义所使用。


宏定义讲解

不定参数函数的实现需要包含stdarg.h头文件的支持。
该头文件中定义了一个宏,而非函数(虽然看起来像函数)来支持不定参数函数的实现。

#include <stdarg.h>

void va_start(va_list list_args, last);

va_start()是要获取参数之前要对va_list进行初始化操作,所以va_start()必须在va_arg()va_end()之前执行。
last参数是指在函数声明中确定类型及名称的最后一个参数。这个last参数不应代表寄存器变量、函数指针或数组。

type va_arg(va_list ap, type);

va_arg()函数第一次执行时返回va_start()last的下一个参数,之后的每次执行都会继续返回va_list的下一个;第二个参数type指定当前要返回的参数的具体类型。
如果va_list已经到结尾了,或者type与参数的实际类型是不符合的,则会发生未知的错误。

void va_end(va_list ap);

va_end()必须与va_start()在同一个函数体中成对出现,在执行完va_end()之后va_list ap就被失去了定义了,变为不可用状态。
如果想多次遍历va_list ap,则可以通过再次调用va_start()后继续遍历,在遍历结束后要相应的调用va_end()结束遍历。

void va_copy(va_list dest, va_list src);

va_list进行复制。src是执行va_start()之后已经初始化了的。且在src执行完va_end()之前,不能将dest用于另一个遍历的过程。


示例代码:实现print()函数

以下代码模拟实现了系统函数printf()的功能:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

// Created by sodino on 15-3-13.
// Copyright (c) 2015年 sodino. All rights reserved.

void my_print(char *fmt, ...) {
char *tmp;
va_list list_args;
int d;
char c, *s;
int isFlag = 0;
va_start(list_args, fmt);
while (*fmt) {
tmp = fmt;
switch (*fmt++) {
case '%':
if (isFlag) {
printf("%%");// 打印出 '%'
isFlag = 0;
} else {
isFlag = 1;
}
break;
case 's': /* string */
if (isFlag) {
s = va_arg(list_args, char *);
printf("%s", s);
isFlag = 0;
} else {
printf("s");
}
break;
case 'd': /* int */
if (isFlag) {
d = va_arg(list_args, int);
printf("%d", d);
isFlag = 0;
} else {
printf("d");
}
break;
case 'c': /* char */
if (isFlag) {
// 需先转为int再强制转换为char,
c = (char) va_arg(list_args, int);
printf("%c", c);
isFlag = 0;
} else {
printf("c");
}
break;
case 'p':
break;
default:
printf("%c", *tmp);
break;
}
}
va_end(list_args);
}

int main/*05*/ (int argc, char ** argv) {
my_print("%s%s %d %d %c %c varying arguments\n","sodino","@qq.com,",123,456,80,81, &argc);
return EXIT_SUCCESS;
}

最后输出如下:

sodino@qq.com, 123 456 P Q varying arguments
Program ended with exit code: 0