C语言中可变参数是一个很有趣的实现,通过将函数实现为可变参数的形式,可以使得函数可以接受一个以上任意多个参数(不固定的参数个数)。我们非常熟悉的printf函数就是一种变长参数,简化版本的函数原型即为:
int printf(const char * _Format, …);
其中,第一个参数即为必须指定的格式字符串,后面的省略号表示数量不定的参数列表。
我们再来看一个简单的函数,函数功能:实现可以求任意个参数的平均值。
#include<stdio.h>
#include<stdarg.h>
int average(int n, ...)
{
va_list hl;
int i = 0;
int sum = 0;
va_start(hl, n);
for (i = 0; i < n; i++)
{
sum += va_arg(hl, int);
}
return sum / n;
va_end(hl);
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int avg1 = average(2, a, c);
int avg2 = average(3, a, b, c);
printf("avg1=%d\n", avg1);
printf("avg2=%d\n", avg2);
system("pause");
return 0;
}
使用上面提到的三个点表示的省略号即可达到定义一个可变参数的函数的目的,但是函数中如何取出这里面的所有参数呢?
这里要使用到C语言中解决变长参数问题的若干宏定义va_start、va_arg、va_end,他们均定义在stdarg.h头文件中,以va开头(表示variable-argument可变参数),可根据预先定义的系统平台自动获取相应平台上各个数据类型的偏移量。他们的使用方法为:
va_list hl; //定义一个可变参数列表hl
va_start(hl, n); //初始化hl指向参数n的下一个参数
va_arg(hl, type); //获取当前参数内容并将hl指向下一个参数
va_end(hl); //释放hl
首先定义一个va_list类型的变量hl,它用于访问参数列表的未确定部分,
然后使用va_start初始化hl这个变量。va_start的第一个参数是va_list的变量名(即hl),第二个参数是省略号前最后一个有名字的参数(即n)。
初始化之后,hl即指向了参数n后面的第一个参数,即不确定参数中的第一个。
然后为了访问参数使用va_arg,这个宏接受两个参数:va_list变量(即hl)和参数列表中下一个参数的类型。va_arg可以取出当前hl指向的这个参数的数值,并把hl指向了下一个参数,不断的进行这个操作可以取出可变参数中的所有参数。
最后使用va_end清空这个可变参数列表。
可变参数的限制
- 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,如果你想一开始就访问参数列表中间的参数,这是不行的。
- 参数列表中至少有一个命名参数。否则无法使用va_start。
- 这些宏无法直接判断实际存在参数的数量。
- 这些宏无法判断每个参数的类型。
- 如果在va_arg中指定了错误的类型,那么结果是不可预测的。
可变参数的应用:模拟实现printf函数
#include<stdio.h>
#include<stdarg.h>
void Printf_int(int n)
{
if (n > 9)
{
Printf_int(n / 10);
}
putchar((n%10) + '0');
}
int my_printf(const char* format, ... )
{
va_list hl;
va_start(hl, format);
const char* p = format;
while (*p != '\0')
{
if (*p == '%')
{
p++;
switch(*p)
{
case 'd':
{
int d = va_arg(hl, int);
Printf_int(d);
}
break;
case 'c':
{
char ch = va_arg(hl, char);
putchar(ch);
}
break;
case 's':
{
char* cp = va_arg(hl, char*);
while (*cp)
{
putchar(*cp);
cp++;
}
}
break;
}
}
else
{
putchar(*p);
}
p++;
}
va_end(hl);
}
int main()
{
int a = 12;
char c = 'C';
char* msg = "hello HL!";
my_printf("printf result is: %d,%c,%s.hello HL!\n",a,c,msg);
system("pause");
return 0;
}
可变参数理解是在函数栈帧形成过程的理解的基础上的,栈帧的形成很重要,对代码的理解有很大的帮助,还不太懂的同学可以考虑恶补一下栈帧相关的知识
不足或者过程不清晰的地方,还望多多指出~~~~