赞
踩
C 语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数(mandatory argument),后面是数量可变的可选参数(optional argument)。
这种函数必须至少有一个强制参数。可选参数的类型可以变化。可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。
平时我们写C语言函数时,一般是固定参数的,但是像打印输出格式化内容时,其参数个数就不确定了,类似如下:
printf("This is a sample, %s, %d, %u", "variable-argument", -20, 50);
对printf函数来说,需要格式化的内容是怎样的形式,完全由程序员决定,这就导致参数个数不定(从第二个参数开始,第一个参数是固定的字符串类型),因此printf的函数原型只能采用可变参数的形式。下面是C语言中printf原型:
extern int printf(const char *format,...);
对于每一个强制参数来说,函数头部都会显示一个适当的参数,像普通函数声明一样。参数列表的格式是强制性参数在前,后面跟着一个逗号和省略号(...),这个省略号代表可选参数。
可变参数函数要获取可选参数时,必须通过一个类型为 va_list 的对象,它包含了参数信息。这种类型的对象也称为参数指针(argument pointer),它包含了栈中至少一个参数的位置。可以使用这个参数指针从一个可选参数移动到下一个可选参数,由此,函数就可以获取所有的可选参数。va_list 类型被定义在头文件 stdarg.h 中。va在这里是variable-argument(可变参数)的意思。
当编写支持参数数量可变的函数时,必须用 va_list 类型定义参数指针,以获取可选参数。在下面的讨论中,va_list 对象被命名为 argptr。可以用 4 个宏来处理该参数指针,这些宏都定义在头文件 stdarg.h 中:
void va_start(va_list argptr, lastparam);
宏 va_start 使用第一个可选参数的位置来初始化 argptr 参数指针。该宏的第二个参数必须是该函数最后一个有名称参数的名称。必须先调用该宏,才可以开始使用可选参数。
type va_arg(va_list argptr, type);
展开宏 va_arg 会得到当前 argptr 所引用的可选参数,也会将 argptr 移动到列表中的下一个参数。宏 va_arg 的第二个参数是刚刚被读入的参数的类型。
void va_end(va_list argptr);
当不再需要使用参数指针时,必须调用宏 va_end。如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针,也必须先调用宏 va_end。
void va_copy(va_list dest, va_list src);
宏 va_copy 使用当前的 src 值来初始化参数指针 dest。然后就可以使用 dest 中的备份获取可选参数列表,从 src 所引用的位置开始。
- typedef int *va_list[1];
-
- #define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
- #define va_arg(ap, type) __va_arg(*(ap), type)
- #define va_end(ap) ((void)(*(ap) = 0))
它的实现原理利用了内存的压栈技术,将参数压入(push)栈内,使用时,再逐个从栈里pop出来。需要注意的是,压栈的顺序是从最右边参数开始的,再向左逐个压入,根据栈的原理,在取参数时,就从第一个可变参数开始了。
在VS2017-X86平台下的源码:
- typedef char* va_list;
- #define _ADDRESSOF(v) (&(v))
- #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
-
- #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
- #define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
- #define __crt_va_end(ap) ((void)(ap = (va_list)0))
-
- #define va_start __crt_va_start
- #define va_arg __crt_va_arg
- #define va_end __crt_va_end
- #define va_copy(destination, source) ((destination) = (source))
- 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
- 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
- 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 >- va_start 是在 stdarg.h 头文件中定义的。
- 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
- 使用宏 va_end 来清理赋予 va_list 变量的内存。
- #include <stdarg.h>
- #include <stdio.h>
-
- void variable_argument(int fix_argument1, int fix_argument2, ...)
- {
- va_list ptr;
-
- va_start(ptr, fix_argument2);
- int first = va_arg(ptr, int);
- int second = va_arg(ptr, int);
- int third = va_arg(ptr, int);
- char* four = va_arg(ptr, char*);
- float five = va_arg(ptr, double);
-
- va_end(ptr);
-
- printf("first is %d, second is %d, third is %d, four is %s, five is %f\n", first, second, third, four, five);
-
- return;
- }
-
- int main(int argc, char** argv[])
- {
- variable_argument(1, 2, 5, 2, -5, "Hello", 3.14159);
- return 0;
- }
- // 函数add() 计算可选参数之和
- // 参数:第一个强制参数指定了可选参数的数量,可选参数为double类型
- // 返回值:和值,double类型
- double add(int n, ...)
- {
- int i = 0;
- double sum = 0.0;
- va_list argptr;
- va_start(argptr, n); // 初始化argptr
- for (i = 0; i < n; ++i) // 对每个可选参数,读取类型为double的参数,
- sum += va_arg(argptr, double); // 然后累加到sum中
- va_end(argptr);
- return sum;
- }
-
- //double s = add(5, 1.0, 2.0, 3.0, 4.0, 5.0);
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
-
- int average(int n, ...)
- {
- va_list arg;
- int i = 0;
- int sum = 0;
- va_start (arg, n);
- for (i=0; i<n; i++)
- {
- sum += va_arg(arg, int);
- }
- return sum/n;
- va_end (arg);
- }
-
- int main()
- {
- int ret1 = average (5, 4, 5, 6, 7, 8);
- int ret2 = average (3, 4, 5, 6);
- printf ("ret1 = %d, ret2 = %d\n", ret1, ret2);
- system ("pause");
- return 0;
- }
参考文章:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。