一、定义及表达方式

可变参数,顾名思义,其参数是可变的,具体指的是其参数的个数是可变的,即函数参数数目可变,其一般格式为:

type VarArgFunc(type FixedArg1, type FixedArg2,...)

其中,参数可分为两部分:数目固定参数和数目可变参数。函数至少需要一个固定参数,固定参数的声明和普通参数一样,可变参数由于个数不确定,声明时用“…”表示。注意,固定参数和可变参数共同构成参数列表。

二、编写可变函数准备知识

要使用可变函数,需要引入头文件 #include “stdarg.h” ,要使用可变函数,需要先认识几个宏定义:

宏定义 作用
va_list arg 定义一个指向个数可变的参数列表指针
va_start(arg,n) 使参数列表指针arg指向函数参数列表中的第一个可选参数
va_arg(arg,type) 返回参数列表中指针arg所指的参数,返回类型为type,并使指针arg指向参数列表中的下一个参数
va_end(arg) 清空参数列表,并置参数指针arg无效

下面,具体看看这几个宏定义在VS2008中的内容:

//定义一个指向个数可变的参数列表
typedef  char* va_list;    //定义va_list 是一个字符指针

可以看出,va_list arg的作用等同于char* arg。

//使参数列表指针ap指向函数参数列表中的第一个可选参数
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
//其中_INTSIZEOF(v)是指如果v是1,2,3,4个字节,则返回为4,如果为5,6,7.8个字节,则返回为8,类似于整型提升的作用
//v为可变参数的的前一个参数,取出该参数的地址,并对其做地址的提升,指向下一个参数(即指向第一个可变参数)

可以看出,va_start(arg,n)是使参数列表指针arg指向函数参数列表中的第一个可选参数。

//返回参数列表中指针ap所指的参数,返回类型为type,并使指针p指向参数列表中的下一个参数
#define va_arg(ap,t)  (*(t *) ((ap +=_INTSIZEOF(t))-_INTSIZEOF(t)))
//清空参数列表,并置参数指针ap无效
#define va_end(ap)   (ap = (va_list)0 )

三、实例说明

下面,让我们以一个求任意个参数的平均值的函数为例:

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

int average(int n, ...)
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for(i=0; i<n; i++)
    {
        sum += va_arg(arg, int);
    }
    sum = sum/n;
    va_end(arg);
    return sum;
}

int main()
{
    int a = 4;
    int b = 5;
    int c = 7;
    int avg1 = average(2, a, c);
    int avg2 = average(3, a, b, c);
    printf("avg1 = %d\n", avg1);
    printf("avg2 = %d\n", avg2);
    return 0;
}

函数先出main()函数开始运行,执行到int avg1 = average(2, a, c);时调用average(int n, …),将实参2传给形参n,此时函数的栈帧如下图所示:
lua可变参数函数 可变参数列表_函数参数
进入average函数后,先执行va_list arg;定义一个char*的变量arg,接着定义两个临时变量,在执行下一步:

va_start(arg, n);
//这句话定义为( arg = (va_list)&n + _INTSIZEOF(n) )
//取出n的地址,并使arg指向n一个下一个地址,即指向a。(此时arg指向a)

接着循环求和n次:

sum += va_arg(arg, int);
//va_arg(arg, int)表示:(*(int *) ((arg +=_INTSIZEOF(int))-_INTSIZEOF(int)))
//sum += (*(int *)((arg += 4)-4))
//每次拿到arg所指向的内容,并使arg向下偏移一个地址,为下次取内容做准备

求和之后,将其平均值赋给sum。

va_end(arg);  //清空参数列表,并置参数指针arg无效

最后将sum返回。average函数调用完毕,完成对average函数的栈帧进行清栈。程序返回main()函数继续执行。

最后,小结一下,可变参数的函数就是:通过va_start初始化参数列表,然后使用va_arg从参数列表中取出你想要的参数,最后调用va_end执行清理工作。