可变参数编程:
一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的实际参数。但在某些情况下我们希望函数的
参数个数可以根据需要确定,因此c语言引入可变参数函数。
典型的可变参数函数的例子有printf()、scanf()等
int printf(
const
char *
format, ...);
int scanf(
const
char *format, ...);
编写可变函数准备:
为了编写可变参数函数,我们通常需要用到头文件下定义的以下函数:
为了编写可变参数函数,我们通常需要用到头文件下定义的以下函数:
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);
其中:
va_list是用于存放参数列表的数据结构。
va_start函数根据初始化last来初始化参数列表。
va_arg函数用于从参数列表中取出一个参数,参数类型由type指定。
va_copy函数用于复制参数列表。
va_end函数执行清理参数列表的工作。
va_list是用于存放参数列表的数据结构。
va_start函数根据初始化last来初始化参数列表。
va_arg函数用于从参数列表中取出一个参数,参数类型由type指定。
va_copy函数用于复制参数列表。
va_end函数执行清理参数列表的工作。
上述函数通常用宏来实现,例如标准ANSI形式下,这些宏的定义是:
typedef char * va_list; //字符串指针
#define _INTSIZEOF(n) ( (sizeof(n) sizeof(int) - 1)& ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap = _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
#define _INTSIZEOF(n) ( (sizeof(n) sizeof(int) - 1)& ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap = _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
下面举一个简单的例子:
求平均数,我们对于求多少个数的平均数未知,所以我们写一个可变参数函数
int Avg(char n,...) //n的第一个作用用于提示...的位置,n的第二个作用用于记录...数据的个数
{
int sum = 0;
va_list list; //定义指针,char *list;
va_start(list,n); //将指针定位到...的开头
for(int i=0;i
{
sum = va_arg(list,int); //从...中取数据
}
va_end(list); //关闭指针list = NULL;
return sum/n;
}
int main()
{
printf("%d\n",Avg(1,10));
printf("%d\n",Avg(2,10,20));
printf("%d\n",Avg(3,10,20,30));
printf("%d\n",Avg(4,10,20,30,40));
printf("%d\n",Avg(5,10,20,30,40,50));
}
简单说说可变参数实现原理:
函数的所有参数是存储在线性连续的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。
按照C标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统C有区别,传统C允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址。
仅从函数原型我们是无法确定"..."中有几个参数、参数都是什么类型的。回想一下函数传参的过程,无论"..."中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置