美文网首页C语言
可变参数列表

可变参数列表

作者: 风情云 | 来源:发表于2020-04-01 12:02 被阅读0次

    在使用函数函数时,函数得形参并没有确定,这时就需要一个可变形参的出现。可变参数列表完全符合这个要求。其实,在一开始时就接触可变参数列表,就是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);
    

    [图片上传失败...(image-1c5e78-1585713718399)]
    可以看到类型会自动提升,形参只接受几种数据类型
    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函数中的一致,注意形参中数据类型自动提升。读取形参数据只能顺序读取,不可以跳过某个值。


    微信号

    相关文章

      网友评论

        本文标题:可变参数列表

        本文链接:https://www.haomeiwen.com/subject/qxvouhtx.html