在函数原型中,列出了函数期望的参数,但原型只能显示固定数目的参数,在求平均数的时候,如果数字有少数的几个,那么我们可以依次给函数传参,可是,如果数字多的话,难道要写出每个形参吗?那么让一个函数在不同时候接受不同数目的参数是否可以呢?答案是ok的,因为C语言很聪明,提供了一系列的可变参数来解决这个问题。
1,stdarg宏
可变参数可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中。这个头文
件声明了一个类型va_list和三个宏va_start,va_arg和va_end。
(1)va_list:声明变量,用于访问参数列表的未确定部分;
(2)va_start:初始化va_list申明的变量,它的第一个参数是va_list变量的名字,第二个参数是省略号前最后一个有名字的参数。通过初始化把va_list变量设置为指向可变参数部分的第一个参数;
(3)va_arg:用于访问参数,它的第一个参数是va_list变量的名字,第二个参数是参数列表中下一个参数的类型。在有些函数中,参数类型都是一样的,但有些函数可能要通过前面获得的数据来判断下一个参数的类型。va_arg返回这个参数的值,并使va_list变量指向下一个可变参数,如果此时已经是最后一个参数,则指向NULL。
(4)va_end:当访问最后一个可变参数之后,用va_end置空va_list变量.
注意:可变参数必须从头到尾按照顺序逐个访问,但也可以在访问几个参数之后半途中止。但是,不能从中间开始访问。此外,由于参数列表中的可变参数部分并没有原型,所以,所有作为可变参数传递给函数的值都将执行缺省参数类型提升。
2.代码实现:
实现可变参数函数:average,求平均值。
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.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);//得到下一个可变参数的值
}
va_end(arg);
return sum/n;
}
int main()
{
int ret = 0;
ret = average(5,1,2,3,4,5);
printf("%d\n",ret);
return 0;
}
实现可变参数函数:Max,求最大值。
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
int Max(int n,...)
{
va_list arg;
int i = 0;
int max = 0;
va_start(arg,n);//</span><span style="line-height: 26px; font-family: 宋体;"><span style="font-size:18px;">以固定参数的地址为起点确定可变参数的内存起始地址</span></span><span style="font-size:24px;">
for(i=0; i<n; i++)
{
int tmp = 0;
tmp = va_arg(arg,int); //得到下一个可变参数的值
if(tmp > max)
max = tmp;
}
va_end(arg);
return max;
}
int main()
{
int ret = 0;
ret = Max(5,1,2,3,4,5);
printf("%d\n",ret);
system("pause");
return 0;
}
3.可变参数在编译器中的处理
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 )
代码的含义:
(1)首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的
(2)定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。
(3)va_start的定义为 &v+_INTSIZEOF(v)
,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap,v)以后,ap指向第一个可变参数在的内存地址.(4)va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
(5)va_end宏的解释:ap=(char*)0.
4.实现简单的printf可变参数的函数。
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
int my_printf(char *val,...)
{
va_list arg;
va_start(arg,val);
while(*val)
{
if(*val == 'c')
putchar(va_arg(arg,char));//输出字符
else if(*val == 's')
puts(va_arg(arg,char*));//输出字符串
val++;
}
va_end(arg);
}
int main()
{
my_printf("s ccc","hello", 'b','i','t');
return 0;
}
注意:如果在va_arg中指定了错误类型,其结果不可预测。