C语言:浅谈可变参数函数

说起函数,我们可能第一时间就想到参数是不能改变的。但有一种可变参数的函数,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个不固定的参数,是不是很神奇呢,下面我们就通过一个例子来详细说一下这个可变参数到底是怎么一回事

首先看代码,这个代码里面有比较详细的注释,大家可以先看看是否可以看懂

#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>

int Aver(int n, ...)
{
    int sum = 0;
    va_list arg;//char* arg 定义arg变量
    va_start(arg, n);//初始化arg变量 指向要计算的第一个元素 arg=(char*)(&n)+4
    int i = 0;
    for (i = 0; i < n; i++)
    {
        sum = sum + va_arg(arg, int);//(*(int*))((arg+=4)-4) 指向下一个元素 但返回的是当前元素
    }
    va_end(arg, n);//char* 0
    return sum/n;
}

int main()
{
    int ret = 0;
    ret = Aver(3, 4, 6, 8);
    printf("平均值为:%d\n", ret);
    system("pause");
    return 0;
}

我们来分析一下这个代码

首先这个代码是用来实现求任意多个数的平均值,它在main函数里面调用了函数Aver函数,而这个Aver函数的参数部分我们看到和平常的函数有点不一样,实际上这个Aver函数就是可变参数函数,也就是它的参数是可变的。

再走进Aver这个函数里面,我们看到有一个语句:

va_list arg;

这个语句声明一个va_list类型的变量,它用于访问参数列表的未确定部分。实际上这个va_list类型就是char*类型,它通过typedef重定义为va_list类型,也就是下面展示的样子

typedef char *  va_list;

接下来还有一个语句:va_start(arg,n);这个语句是对变量arg进行初始化,初始化成什么呢,我们看下面这个代码

#define va_start _crt_va_start

我们看到这个语句是宏重定义的一个名称,它的真正执行的语句是后面的,_crt_va_start,我们发现这个名称我们也不认识,那么再看下面这个代码

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

还是一个宏,我们依次将它替换,直到替换到我们可以看懂的代码,下面两个语句是对INSIZEOF和ADDRESSOF的替换

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define _ADDRESSOF(v)   ( &(v) )

最后全部替换的结果是下面的语句

arg=(char*)(&n)+4

那么这个语句我们就可以清楚的看出来,原来的那个我们看不懂的语句,即va_start(arg,n)的意思就是将变量arg进行初始化, 初始化为函数的第二个参数,即可变参数的第一个。

接着循环内部还有一个我们看不懂的语句,即

va_arg(arg,int);

那我们继续按照上面的方法转到它的定义,将它转换为我们可以看懂的语句,看下面的代码

#define va_arg _crt_va_arg

_crt_va_arg继续转到定义,看代码

#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

INTSIZEOF继续转到定义,得到下面的代码

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

至此,转换到这,已经几乎没有我们不认识的代码了,我们将它全部替换掉之后,会发现原来代码即va_arg(arg,int)被替换为下面的代码

(*(int*))((arg+=4)-4)

现在这个语句是我们可以看懂的了吧,它的意思是将arg变量的地址指向下一个元素的位置,但之后又减4,表示返回的值又回到了它所在的位置。所以总结来说就是指向下一个元素的地址,返回该元素的值。

最后还有一个我们不认识的语句:

va_end(arg,n);

继续刚刚的方法,找到这个语句,转到它的定义,那么我们可以看到下面这个代码

#define va_end _crt_va_end

再将_crt_va_end转到定义,看代码

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

我们看到( ap = (va_list)0 )这个代码,根据上面(va_list arg)这个代码的解释,它应该被替换为(ap=(char*)0),这样我们就将最初我们不认识的代码翻译为((char*)0)。

至此,我们通过对可变参数的分析,这个使用可变参数实现函数求函数参数的平均值的代码替换为我们可以认识的代码了。

下面说几点使用可变参数函数的注意点:

  1. 可变参数必须从头到尾依次访问,可以在访问几个可变参数之后半途终止。但是不能一开始就访问参数列表中间的参数。
  2. 参数列表中至少有一个命名参数,如果连一个命名参数都没有,就无法使用va_start。因为va_start即((char*)(&n))+4,如果连一个命名参数都没有,就不能根据它的地址找到可变参数的第一个元素。
  3. 这些宏无法直接判断实际存在参数的数量。
  4. 这些宏无法判断每个参数的类型。
  5. 如果在va_arg中指定了错误的类型,那么其后果是不可预测的。因为va_arg要通过类型来确定指针向后移动几位才会指向下一个元素。如果指定了错误的类型,就很可能找不到下一个元素,造成无法预测的后果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值