可变参数列表

可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中,这个头文件声明了一个类型va_list和三个宏——va_start, va_arg,和va_end我们可以通过声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。
下面就让我们通过代码深入的看看va_list 和那三个宏到底是什么?

#include<stdio.h>
#include<stdarg.h>
float average(int n_values,...)
{
    va_list var_arg;//char *var_arg
    int count;
    float sum=0;

    /*准备访问可变参*/
    va_start(var_arg,n_values);// 指针var_arg后移动4个字节,就是跳过n_values的内存地址 开始获取可变参数列表中的第一个参数

    /*添加取自可变参数列表的值*/
    for(count=0;count<n_values;count++)
    {
        sum+=va_arg(var_arg,int);//往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数) 循环获取到可变参数列表中的参数返回给sum
    }
    /*完成处理可变参数*/
    va_end(var_arg);//访问结束让指针var_arg指向NULL
    return sum/n_values;
}
void main()
{
    float ave=average(3,10,20,30);
    printf("%f",ave);
}

这里写图片描述
va_list 其实就是char *类型 那么va_list var_arg 其实就是 char *var_arg

宏va_start
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述
(sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)=(4+4-1)&~(3)=8&~3=0111&1100=0100=4
那不就是sizeof(n_values) 吗?(4字节)
那么ap=(char )&v+4 在我们代码中就是 var_arg=(char )n_values+4 即指针变量var_arg跳过n_values的地址后移4字节指向n_values的下一个变量(也就是我们第一个可变参数)的地址。
由栈图我们可以看出此时var_arg的指向
这里写图片描述
总结一下宏va_start的作用就是指针var_arg后移动4个字节,就是跳过n_values的内存地址开始获取可变参数列表中的第一个参数

宏va_arg
这里写图片描述
这里写图片描述
(_INTSIZEOF(t)在va_start中已经解析过了)
可以看出 ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 就是((int ))((var_arg+=4)-4)
(var_arg+=4)就是把指针var_arg向下偏移4字节此时var_arg的指向如图2所示指向了下一个参数的地址
这里写图片描述
那么((var_arg+=4)-4) 就是偏移后的var_arg再向上**偏移**4字节,但是此时的偏移指针var_arg并没有移动,只是把var_arg-4指向的地址中的值返回给sum(通俗一点就是把偏移后的var_arg指针赋给一个临时指针让这个临时指针向上偏移4字节,并把临时指针指向的地址的值返回给sum)。看栈图我们就可以更清楚的看到var_arg到底是怎么移动的怎么返回的,如图3:
这里写图片描述 图3
有人会问((int ))((var_arg+=4)-4)里面有+4-4那我可不可以把里面+4-4约掉? 答案:不可以。先让我们看看约掉后的结果((int ))(var_arg),如图4
这里写图片描述图4
总结一下宏va_arg(var_arg,n_values)的作用就是把var_arg往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数) 这样就可循环获取到可变参数列表中的参数

宏va_end
这里写图片描述
这里写图片描述
也就是ap=(char )0 在我们代码中 就是 var_arg=(char )NULL,va_end在程序中作为一个可变参数列表的结束标志,把NULL赋给指针var_arg防止它变为野指针。

分析到这里我们可变参数列表中的va_list和3个宏就都已经分析完了,

下面是我自己用可变参数列表实现的一个程序 (找出4个数中的最大值)

#include<stdio.h>
#include<stdarg.h>
int max_list(int n,...)
{
    char *p;
    int count;
    int temp;

    va_start(p,n);  //准备访问可变参
    int max=va_arg(p,int);//max中保存第一个参数的值 

    for(count=0;count<n;count++)
    {
        if(max<(temp=va_arg(p,int)))  //创建临时变量temp接收参数  和max比较 若大于max则把temp赋给max 
        {
            max=temp;
        }
    }//添加取自可变参列表的值

    va_end(p);  //完成处理可变参数
    return max;
}
void main()
{
    int max=max_list(4,20,50,8,40);
    printf("%d\n",max);
}

结果:这里写图片描述

在做可变参数列表时我发现,上面的所有可变参数都是int型的,就算你用字符作为参数(char c = ‘a’),在可变参数里面(三个小点里面)也同样会为他分配4个字节地址空间(至于为什么都会给它分配4字节空间,不明白的说明前面介绍的可变参数列表的三个宏你还没有看明白);浮点型也是如此,那是因为这些宏存在两个限制:
1,无法判断每个参数的类型,
2,无法判断实际存在参数的数量。

要解决这些问题就必须使用命名参数,命名参数指定了实际存在的参数的个数,不过他们的类型都被假定为整型,例如:printf函数中的命名参数都是格式字符串,它不仅制定了参数的个数,还指定了参数的类型。

那我们就要考虑如何解决让可变参数列表中可以使用浮点型、字符串呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值