可变参数列表解析:
可变参数是指,函数部分的参数个数不是固定的,每次根据需求可以传入不同个数的参数。实际上,printf()函数;就是一个典型的可变参数函数,可以打印一个字符串,也可以打印多个字符串。
例如:
int main()
{
printf("%d\n ",2); //有一个参数
printf("%s %d\n","sunshine",3); //有两个参数
printf("%s %s %d\n","sunshine", "girl",3); //有三个参数
system("pause");
return 0;
}
结果:
下面以一个求平均值的代码来详细解释可变参数列表:
#include<stdarg.h>
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;
}
int main()
{
int av = average(3, 4, 5, 6);
printf("%d\n", av); //5
av = average(4, 2,4,6,8);//5
printf("%d\n", av);
system("pause");
return 0;
}
下面对是对以上代码的解释:
#include<stdarg.h> //可变参数对应的头文件
average(int n, ...) // 不知道几个参数,所以用...表示,叫做未知参数列表,n代表后面有多少个参数
{
va_list arg;
// char* arg
int i = 0;
int sum = 0;
va_start(arg, n);
//va_start_a(arg, n) ((void)(arg = (cha*)&(n) + _INTSIZEOF(n))) // 向上取一个整型的大小 如果n是一个字节,则 _INTSIZEOF(n) 为4
//( arg = (char*)&(n) + 4 ) // 函数调用过程中,由于在栈空间开辟的过程中,先将函数参数右边的参数压栈,所以栈顶上的元素,是传的参数个数,因此,arg指向未知参数中的第一个参数
for (i = 0; i < n; i++)
{
sum += va_arg(arg, int);
// va_arg(arg, int) ( *(int*)((arg += 4 ) - 4 ))
//sum = sum + ( *(int*)((arg += 4 ) - 4 )) //这句代码写的相当巧妙,完成了两个动作。
//1.arg移动4个字节,指向未知参数的下一个元素,再赋给arg,使得arg的位置发生改变
//2.同时 ((arg += 4 ) - 4 ) 中 要注意-4,回到了第一个元素的位置,但是由于没有赋值,arg所指的仍然是下一个位置
}
va_end(arg);
//#define va_end(arg) ((void)(arg = (char*)0))
//((void)(arg = (char*)0))
return sum / n;
}
int main()
{
int av = average(3, 4, 5, 6);
printf("%d\n", av); //5
av = average(4, 2,4,6,8);//5
printf("%d\n", av);
system("pause");
return 0;
}
- 声明一个va_list 类型的变量 arg,它用于访问参数列表的未确定部分。
- 这个变量是调用 va_start 来初始化的。它的第一个参数是va_list的变量名,第二个参数是省略号前面最后一个有名字的参数(因为对于不同的函数来说,省略号之前可能会有多个参数)。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
- 为了访问参数,需要使用va_arg,这个宏接收两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中所有的可变参数都是整型。va_arg返回这个参数的值,并使用va_arg指向下一个可变参数。
- 最后,当访问完毕最后一个可变参数之后,我们需要调用va_end。
可变参数列表的练习:
模拟实现库函数中的 printf() 函数,函数原型为 print(char *format, …)
void show(int n)
{
if (n > 9)
{
show(n / 10); //递归,拆分整型每一位
}
putchar(n % 10 + '0'); //整型+'0' 结果是 整型对应的字符,比如,整型4+'0' 对应是字符'4'
}
print(const char *format, ...)
{
va_list arg;
va_start(arg, format); //让arg指向未知参数列表前第一个有名字的参数(format),初始化过程
while (*format)
{
switch (*format)
{
case 's': //遇到s 说明是个字符串
{
char* ret = va_arg(arg, char*); //取出的是 字符串首字符的地址
while (*ret)
{
putchar(*ret);
ret++;
}
}
break;
case 'd': //遇到d 说明是个整型
{
int ret = (int)va_arg(arg, int);
show(ret);
}
break;
case 'c':
{
char* ret = va_arg(arg, char*);
putchar(ret);
}
break;
default: // 既不是字符串,也不是整型,字符,就正常输出
{
putchar(*format);
}
break;
}
format++;
}
va_end(arg);
}
int main()
{
print("s cccc d !~~\n", "beautiful", 'c', 'o','o', 'l',100); //函数传参,需要传类型 和 内容
system("pause");
return 0;
}
输出结果:
ps:补充一个知识点:库函数中printf() 函数的返回类型 是整型,返回值是打印在屏幕上字符的个数。
可变参数的注意事项:
- 可变参数必须从头逐个访问。允许在访问了几个可变参数之后在中途停止,但是,不允许一开始就访问参数列表中间的参数。
- 参数列表中至少有一个命名参数。如果连一个命名参数都没有,那就无法使用va_start。
- 这些宏是无法直接判断每个参数的数量。
- 这些宏无法判断每个参数的类型。
- 如果在va_arg中指定了错误的类型,它就会按照错误的类型的往下查找,结果肯定是错误的。