C语言的可变参数列表是一项非常有意思的技术,它可以使函数接收不确定个数个参数,来达到某些特定场合的要求。
另外可变参数列表与C程序的栈帧是紧密结合的,所以,建议大家先参考一下这篇讲解C语言栈帧的文章
来看一个例子:
#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);
}
va_end(arg);
return sum / n;
}
本例就是应用了可变参数列表,其中出现了几个 va 前缀的类型和新用法,这就是C语言可变参数列表的语法,接下来我们研究一下这些。
1.va_list arg;
创建一个可变参数,本质上是一个 char* 的指针。
2. va_start
用可变参数列表中的已知参数初始化可变参数。
我们转到 va_start 的定义,可以看到, va_start 是一个宏,修饰了一个叫 _crt_va_start 的东西。
我们再转到 _crt_va_start,在这里可以看到它的定义。
我们再看一下这个 [_ADDRESSOF和 _INTSIZEOF 是什么。
看到这里,想必大家都明白了吧。va_start翻译过来其实就是:
arg = char*(&n) + /*向上取整型,n = 1,2,3,4为4,5,6,7,8为8,类推*/
// va_start:初始化arg,让arg指向未知参数部分的第一个参数
// n为可变参数列表未知参数前面第一个有名字的参数(已知参数)
// 不一定为可变参数参数列表第一个参数,因为可能会有多个已知参数
这里运用了栈帧的原理,因为进入 add 函数时参数压栈顺序是从左到右,所以可变未知参数是先于已知参数压栈的,所以知道可变未知参数前第一个已知参数,就可以将 arg 定位到第一个未知参数,直接在栈上找到它。
3. va_arg
我们转到 va_arg 的定义来看一下。
和 va_start 一样,这里也进行了封装,我们继续。
这里运用到了两次 _INTSIZEOF 宏,我们再看一下。
还是函数栈帧的使用,我们将例子中的 va_arg 语句翻译一下。
sum += va_arg(arg, int);
// define va_arg(arg,int) (*(int *))((arg += _INTSIZEOF(int)) - _INTSIZEOF(int))
// ==> sum += (*(int *)((arg += 4) - 4));
因为 arg 是一个 char* 指针,参数是一个 int类型,所以每次给 arg 加4 即指向下一个未知参数。因为参数是从左到右传入,所以加4即下一个未知参数。然后sum又是arg-4,即当前未知参数。
4. va_end
因为 arg 是一个指针变量,使用完必须进行销毁,所以想必 va_end 的作用就是进行指针销毁。
打开 va_end 的定义。
不出意外,它也是做了封装,我们继续点进去。
这就是它的庐山真面目了。
5. 可变参数的限制
可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途中止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的(可以把想访问的中间参数之前的参数读取但是不使用,曲线救国)。
参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
这些宏是无法直接判断实际实际存在参数的数量。
这些宏无法判断每个参数的类型。所以在 va_arg 的时候一定要指定正确的类型。
如果在va_arg中指定了错误的类型,那么后果是不可预测的
第一个参数也未必要是可变参数个数,例如printf()。
练习:编写printf,返回打印字符个数,只有s c d 字符串 字符 整形 三个选项
#include <stdio.h>
#include <stdarg.h>
void display(int n,int* count)
{
if (n > 9)
{
display(n / 10,count);
}
(*count)++;
putchar(n % 10 + '0');
}
int print(const char* format,...)
{
int count = 0;
va_list arg;
va_start(arg, format);
while (*format != '\0')
{
switch (*format)
{
case 's':
{
char* ptr = va_arg(arg, char*);
while (*ptr != '\0')
{
putchar(*ptr);
ptr++;
count++;
}
}
break;
case 'd':
{
int ret = va_arg(arg, int);
display(ret,&count);
}
break;
case 'c':
{
char ret = va_arg(arg, char);
count++;
putchar(ret);
}
break;
default:
putchar(*format);
count++;
break;
}
format++;
}
va_end(arg);
return count;
}
int main()
{
char* p = "abcdef";
int age = 20;
char f = 'f';
int num = print("s d f ", p, age, f);
printf("\n%d\n", num);
return 0;
}