在使用函数函数时,函数得形参并没有确定,这时就需要一个可变形参的出现。可变参数列表完全符合这个要求。其实,在一开始时就接触可变参数列表,就是printf函数
printf("%d %d %d\n",1,2,3);
printf函数内部得参数并没有确定,可变得的。接下介绍可变参数列表:
定义可变参数列表
//返回值类型 函数名 (数据类型 变量名,...)
int add(int count,...);
使用可变形参列表需要使用以下函数,头文件<stdarg.h>
va_list 可变参数列表数据类型
type va_arg( va_list argptr, type );
void va_end( va_list argptr );
void va_start( va_list argptr, last_parm );
void va_copy(va_list dest, va_list src);
首先,必须调用va_start() 传递有效的参数列表va_list和函数强制的第一个参数。第一个参数代表将要传递的参数的个数。
其次,调用va_arg()传递参数列表va_list 和将被返回的参数的类型。va_arg()的返回值是当前的参数。
再次,对所有的参数重复调用va_arg()
最后,调用va_end()传递va_list对完成后的清除是必须的。
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
int add(int num,...)
{
//可变参数类型
va_list list;
//获得形参的参数数据类型与参数个数
va_start(list,num);
int i = 0,sum = 0;
for( ; i<num; i++)
{
//返回当前形参的数据
sum += va_arg(list,int);
}
//形参列表结束
va_end(list);
return sum;
}
int main()
{
int sum = add(5,1,2,3,4,5);
printf("sum = %d\n",sum);
return 0;
}
结果
sum = 15
在可变参数列表中,要注意强制要写的是参数个数,这个不能省略。调用函数时,注意参数要对应函数实现得数据类型,不然会得到错误得结果。参数个数也要对应形参个数,不然也会出错。
int sum = add(2,1.0,2);
printf("%d\n",sum);
结果
1072693248
为什么是错误的结果?想要得到正确的答案要如何修改?就要看看可变参数列表得原理
可变参数列表原理
int a = 0;
printf("%d %d %d \n",a++, ++a, a);
结果
1 2 2
为什么结果不是0 2 2呢?这个和函数形参的传参顺序有关,函数是从右往左传入参数。
形参传参顺序
void Test(int a, int b, int c)
{
printf("a: %u\n",&a);
printf("b: %u\n",&b);
printf("c: %u\n",&c);
}
结果
a: 6356720
b: 6356724
c: 6356728
由结果可以看到,地址是递增的,我们也清楚,先入栈的地址比后入栈的地址高,验证函数形参是从右往左传参的。就可以明白上面的结果为什么是1 2 2。
所以从栈顶到栈底的参数为第一个参数,第二个参数,第三个参数…,
上面的结果可以得出,我们可以通过得到第一个形参的地址,就可以得到后面形参的数据。
void Test(int a, int b, int c)
{
int *ptr = &a;
printf("a: %u\n",*ptr);
printf("b: %u\n",*(ptr+1));
printf("c: %u\n",*(ptr+2));
}
int main()
{
int a = 0, b = 1, c = 2;
Test(a, b, c);
return 0;
}
结果
a: 0
b: 1
c: 2
通过上面的铺垫,可变参数列表也是和上面一样的原理:
//va_list原型 typedef char* va_list;
va_list list; //定义一个指针
//将指针指向第二个形参(第一个形参为形参个数说明,不是实际要参与运算的形参)
//num为形参个数
va_start(list,num);
//通过解引用获得指针指向地址的数据,且更新指针,将指针指向下一个形参
//int为下一个形参的数据类型,注意这个类型要与实际形参要一致,不然出错
va_arg(list,int);
//将指针置为空
va_end(list);
如果在va_arg函数中传入的数据类型与实际传入的数据类型不一致,那么会导致指针递增的指针步长不同,得到错误的数据。这就解释上文的答案为什么是错误的,正确的是如何的?
//与上文代码一致,仅修改这句
sum += va_arg(list,float);
float sum = add(2,1.0,2.0);
printf("%f\n",sum);
结果
在32位平台下,运行错误
这个是因为类型的自动提升,形参的类型会自动提升
形参类型自动提升
char *c;
printf("%c %d %ld %f %lf \n",c,c,c,c,c);
可以看到类型会自动提升,形参只接受几种数据类型
char —> int
float —> double
float会自动提升为double,所以指针步长不一致,如果想要获得正确的结果,就要改成double数据类型。
sum += va_arg(list,double);
double sum = add(2,1.0,2.0);
printf("%lf\n",sum);
结果
3.000000
注意事项
va_arg函数在更新指针时,不能跳过某个形参不去读取,只能顺序读取。如果想要获得某个形参的地址可以使用va_copy函数,得到地址,再通过解引用得到数据。
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
int add(int num,...)
{
va_list list,c_list;
va_start(list,num);
int i = 0;
double sum = 0.0;
for( ; i<num; i++)
{
//指针地址复制
if(i == 0) va_copy(c_list,list);
sum += va_arg(list,double);
}
va_end(list);
//类型强转
printf("%lf\n",*(double *)c_list);
return sum;
}
int main()
{
double sum = add(3,1.0,2.0,3.0);
return 0;
}
结果
1.000000
总结
可变参数列表的参数个数一定不能省略,传参数据类型一定要与va_arg函数中的一致,注意形参中数据类型自动提升。读取形参数据只能顺序读取,不可以跳过某个值。