目录
一、什么是可变参数?
可变参数是一个比较有意思的实现,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数。
一般情况下,我们写的函数参数的数目是固定不变的,调用函数时要给出相应的实参,但有些时候,为了做同样的事情,由于参数个数不同,我们总不能写很多个函数,这就有点冗余了。比如:我们想求两个数或10个数的平均数,可以用同一个函数实现。
二、可变参数的实现
C语言头文件stdarg.h中提供了一个va_list的数据类型和va_start,va_arg,va_end三个宏。
1.声明一个 va_list 类型的变量 arg ,它用于访问参数列表的未确定部分。
2.这个变量是调用 va_start 来初始化的。它的第一个参数是 va_list 的变量名,第2个参数是省略号前最后一个有名字的参数。初始化过程把 arg 变量设置为指向可变参数部分的第一个参数。
3.为了访问参数,需要使用 va_arg ,这个宏接受两个参数: va_list 变量和参数列表中下一个参数的类型。在这个例子中所有的可变参数都是整型。va_arg````返回这个参数的值,并使用 va_arg```指向下一个可变参数。
4.最后,当访问完毕最后一个可变参数之后,我们需要调用 va_end 。
举例:求不同个数的平均数
int avg(int n, ...)
{
va_list arg;
va_start(arg, n);//arg指向可变参数的第一个参数
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += va_arg(arg, int);//返回该参数的值并自动指向下一个参数
}
va_end(arg);
return sum / n;
}
int main()
{
int a = 1, b = 2, c = 3,d=10;
printf("%d\n", avg(4,a, b, c,d));
printf("%d\n", avg(2,a, c));
}
并不是所有的函数都可以使用可变参数列表,使用时要注意:
1>可变参数必须从头到尾逐个访问。如果在访问了几个可变参数之后想半途终止,这是可以的,但是,如果一开始就访问参数列
表中间的参数,那是不行的。
2>参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
3>这些宏是无法直接判断实际存在参数的数量;这些宏无法判断每个参数类型。
三、可变参数源码
可变参数的实现过程是使用宏的封装。只要完成替换,就可以自行分析了。
1.va_list:是一种数据类型,可以说就是char*。
typedef char* va_list;
2.va_start:初始化arg为未知参数列表的第一个参数的地址,va_start其实是一个宏函数,第一个参数是一个可以表示后面的参数个数和参数类型的char* ,第二个参数表示就是用于指明arg是指针名,n代表第一个参数,用于表示起始端。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define _ADDRESSOF(v) (&(v))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
3.va_arg:返回当前参数再指向下一个参数。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
通过 va_arg(arg, int) 拿到后面的参数:
1.ap表示可变参数指针,而t表示数据类型。使用 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) ,传入char,float,double等小于4字节的类型,都会返回4。如果类型大小是,5,6,7的话,则返回8。即返回当前一组数中靠近4的倍数的值。
2.( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 该表达式先让指针ap加上4字节的大小,再把减去4字节大小处所对应的值返回。用t强制类型转换,再解引用,此处 t是传入参数的数据类型。
4.va_end:结束。arg赋值为空指针。
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
四、模拟实现printf函数
模拟实现printf函数,可完成下面的功能
//能完成下面函数的调用。
//print("s ccc d.\n","hello",'b','o','y',100);
void print_num(int n)
{
if (n > 9)
print_num(n / 10);
putchar(n % 10 + '0');
}
void print(const char* format, ...)
{
va_list arg;
va_start(arg, format);
while (*format)
{
switch (*format)
{
case 's':
{
char *str = va_arg(arg, char*);
while (*str)
{
putchar(*str);
str++;
}
}
break;
case 'c':
{
char ch = va_arg(arg, char);
putchar(ch);
}
break;
case 'd':
{
int tmp = va_arg(arg, int);
print_num(tmp);
}
break;
default:
putchar(*format);
break;
}
format++;
}
}