形参中可变参数列表

在使用函数函数时,函数得形参并没有确定,这时就需要一个可变形参的出现。可变参数列表完全符合这个要求。其实,在一开始时就接触可变参数列表,就是printf函数

printf("%d %d %d\n",1,2,3);

printf函数内部得参数并没有确定,可变得的。接下介绍可变参数列表:

定义可变参数列表

//返回值类型 函数名 (数据类型 变量名,...)
int add(int count,...);

使用可变形参列表需要使用以下函数,头文件
形参中可变参数列表_第1张图片
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
#include
#include
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.02);
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。
形参中可变参数列表_第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);

形参中可变参数列表_第3张图片
可以看到类型会自动提升,形参只接受几种数据类型
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
#include
#include
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函数中的一致,注意形参中数据类型自动提升。读取形参数据只能顺序读取,不可以跳过某个值。
形参中可变参数列表_第4张图片

你可能感兴趣的:(c语言,指针,列表)