c语言有时会遇到一些参数可变的函数,如printf(),这些函数内部的参数列表是可变的。
printf()函数原型:
int printf(const char *format,...)
printf()函数是以一个支持可变参数的函数,可以有多个参数,除了format以外,后面跟着的参数的个数和类型是可变的,用…作为一种占位符号。“…”称为可变参数列表,可以用来接受个数和类型不确定的参数。
可变参数列表在C语言中使用这样的三个宏(va_start,va_arg,va_end)和一个类型(va_list)来进行实现的,他们是定义在stdarg.h头文件中的。
1.源码剖析
(1)va_list
typedef char * va_list
定义了一个char*类型的va_list变量
(2)va_start
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
将v取地址,然后将其类型强转成va_list类型,也就是转换成一个char * 类型的指针,然后加上移动_INTSIZEOF(v)个字节的地址赋给ap
(3)va_arg
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
将指针ap指向当前位置移动_INTSIZEOF(t)个字节后的位置,然后取现在的ap所在位置的前 _INTSIZEOF(t)个字节的位置(ap移动前的原位置),并将其强制转换成t类型,然后对其解引用,取出这个地址的内容。
(4)va_end
#define va_end(ap) ( ap = (va_list)0 )
将0强制转换成va_list即char *类型,赋给ap,即将ap置空
2.实例分析
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdarg.h>
int average(int n, ...)
{
va_list arg;//用于访问参数列表的未确定部分
// char * arg;
va_start(arg, n);//用n的地址初始化arg. va_start的第一个参数是va_list的变量名,第二个参数是省略号前最后一个有名字的参数。初始过程把arg变量设置为指向可变参数部分第一个参数
// ( arg = (char *)&n + 4);
//#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
int i = 0;
int sum = 0;
for (i = 0; i < n; i++)
{
sum += va_arg(arg, int);//获取参数va_arg(va_arg list,type),一定要保证参数类型正确,va_arg返回这个参数的值,并且va_arg指向下一个可变参数
// sum += ( *(int *)((arg += 4) -4) );
// #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
}
va_end(arg);//清空arg
//( arg = (char*)0 );
//#define va_end(ap)
return sum / n;
}
int main()//函数功能:求取平均数
{
int ave = average(3, 2, 3, 7);
printf("%d\n",ave);
system("pause");
return 0;
}
3.可变参数的限制
- 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
- 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
- 这些宏是无法直接判断实际存在参数的数量。
- 这些宏无法判断每个参数的是类型。
- 如果在va_arg中指定了错误的类型,那么其后果是不可预测的。