您的位置:首页 > 其它

可变参数列表原理及应用

2015-11-11 12:26 295 查看
什么东东?
写过函数都知道,函数的形参和传入的实参需要一一对应,本来无可厚非,可是有时候我们需要一个函数来处理各种各样的形式,长短不一的参数。
该怎么办?比如,求平均值,求三个的需要写三个参数的函数,四个的需要四个,这样非常难受,不能做到代码重用,那怎么办,c的宏里面到定义了
这么一个东西,来瞧瞧!!
##先不要怕我们来一行一行解释这些这些宏定义,看代码一定不要被大写的宏名吓住##
##后面会讲怎么用,如果只想着用的同学可以直接看后面,讲原理原因是有些语言并没有这些宏,要是使用时就可以自己编写了##
##下面内容中,关于野指针,字节对齐,强制转换等内容请参阅我其他博文##

typedef char *  va_list;

@@@这一行是说va_list就是声明一个字符指针类型

#define va_start   _crt_va_start
#define va_arg   _crt_va_arg
#define va_end    _crt_va_end

@@@上面这三行,就是一般的宏替换,表明va_start出现的地方会被_crt_va_start替换

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
@@@这一句呢直接理解呢不好理解(sizeof()函数用来内存占用字节数,比如int就是四个字节)我们可以带几个值进去



大家会发现这一行的宏定义原来就是把所有的变量的大小对齐到以四为倍数的偏移位上。(也就是字节对齐)

#define _ADDRESSOF(v)   ( &(v) )

@@@出现_ADDRESSOF(v)的地方的意思就是取V的地址

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
@@@_crt_va_start(ap,v)就是我们使用的(上面已经定义)va_start,那上面这一句的意思是什么呢
           (va_list)_ADDRESSOF(v)这句表明把v的地址强制转化为va_list形即char *类型 ,那么这个怎么
            用呢?当用va_start时,ap是你的指针,而v是你的数据多少,比如你要处理十个数据,那么v
            是int类型,v知道了,数据紧接着v在内存里排列,那么(va_list)_ADDRESSOF(v)获取到v的指针后
            再加上内存对齐后v的字节大小,就可以吧ap定位到数据的起始位置。
           
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
@@@先来说(ap += _INTSIZEOF(t)) - _INTSIZEOF(t))有人看到这一句就很疑惑减一个加一个不是什么都没干吗?
           要联系上下文来看t一般是你数据的类型,比如你是int,你要写成va_arg(ap,int),这是为什么呢?
           这句话总得意思是把((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))求得的地址强制转化成(t *)形的指针,然后*
           一下来取这个位置的数据。那么为什么要一加一减?首先ap += _INTSIZEOF(t)是把指针偏移现在数据大小
           的字节数,现在ap已经后移一个_INTSIZEOF(t),(ap += _INTSIZEOF(t)) - _INTSIZEOF(t))就是把ap指针偏移
            后的值再往前一个_INTSIZEOF(t),注意只是偏移指针偏移后的值,并没有给ap赋值,所以ap还是后移了,产
            生了一个临时变量比ap小一个_INTSIZEOF(t),然后取了那个地方的值,这样的写法很是精妙,不仅可以取到
            当前位置的值,而且不用加变量就把指针偏移了,所以用一次这个宏就能取到当前位置数据,并能偏移指针。

#define _crt_va_end(ap)      ( ap = (va_list)0 )

@@@把数据0强制转化为va_list形也就是char*类型指针,即就是把ap赋值为NULL,防止野指针出现,为什么
           不直接赋值NULL,这个因为有些语言没有NULL,是用NUL来表示,但是他们的ascii都是0,方便移植。

#################安静的分割线##############
现在来讲一下怎么用,例如下面是一个变长参数求和函数

int sum(unsigned int n, ...)    //函数名,第一个数据参数个数,...就是省略参数个数形式,不能省略
{
    int sum = 0;
    va_list args;                  //定义一个char*指针(前文原理中有原理)
    va_start(args, n);          //把指针定位到第一个参数地址哪里,n为参数个数
    while (n>0)
    {
       //va_arg是把指针后移并取当前位置的的值
      //int位置写你数据的类型ap每次偏移量就是你的数据类型大小
        sum += va_arg(args, int);
        n--;                                  
    }
    va_end(args);
    return sum;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息