如果我同时想求两个整型变量的平均值,3个整型变量的平均值,4个,5个,那我同时要写多少个函数,来完成同一个功能。其实在c语言库中,很多函数的参数变量都是可变的。不是确定的。比如printf。你能说出printf的变量都有啥不。好像不可以,因为我们每次使用printf时,给的变量都是不一样的。有的时候,只有“aaaaaaaaaaaa”这样的字符常量。有的时候还有(“%d”,1).%c,总是不同的对吧。
我们看一下printf函数的源代码
int printf(const char *format, ...)
{ }
在这个printf函数中,首先我们看见她第一个参数是(const char*format),然后则是“ . . . ”,我们不难看出这" . . . "代表的就是可变参数。在我的上一篇博客中,详细讲解了栈帧这一概念。也知道了,参数存在哪个位置,而且是从右向左存的。 也就是说,只要我们找到左边明确已知的的参数,我们就可以通过地址++,访问其他的参数。右边--。因为从右向左存。所有b,a,num的地址依次减小。
aver(num,a,b);
b |
a |
num |
让我们试着用可变参数裂变写一个求最大值的函数。
int Max(int num, ...)
{
va_list arg;
va_start(arg, num);
int max=arg;
while (num--)
{
int a=(int)va_arg(arg,int);
if(max<a)
max=a;
}
va_end(arg);
return max;
}
在函数中有几个定义的宏,让我们具体看一下:
1. va_list arg; 的意思是定义了一个arg变量,这个arg用于访问参数列表未确定的部分。相当于 一个声明。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
2. va_start(arg,num) 这个宏的意义是对 arg 进行初始化,其第一个参数是va_list变量的名字arg,第二个参数是省略号前最后有明确参数的变量。在初始化的过程,arg被设置为指向可变参数部分第一个参数。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
3. va_arg(arg,int)这个宏的意思时按第二个参数类型读取可变参数列表。va_list定义的变量arg和列表中下一个参数的类型。特别注意va_arg这个宏会返回这个参数的值,并且指向下一个可变参数。
#define va_end(ap) ( ap = (va_list)0 )
4. va_end(arg)宏的意思是使指针置为无效,相当于p=nuLL.
别忘了还要引用对应的<stdarg.h>头文件
通过如上观察可变参数的函数,我们发现可变参数列表是有局限性的。
1.我们至少必须知道一个明确的参数,当然如果明确参数的有多个,选择最右的已知参数,因为它距离可变参数变量最近啊。如果不懂,回看栈帧。
2.宏是无法判断数量的,所有需要把长度传过去。不然可能不会终止的向后,导致报错。
3.宏是无法判断类型的,你定义已什么类型读,就会已什么类型读取。所以,可变参数函数在调用时非明确的实参应该是同一类型的。
了解了可变参数列表可以下一个自己的printf函数了。
通过观察库中printf函数。
int MyPrint(char * format,...)
{
va_list arg;
__crt_va_start(arg, format);
while ((*format)!='\0')
{
if ((*format) == '%')
{
format++;
if ((*format) == 'd')
PutInt(__crt_va_arg(arg,int));
else if ((*format) == 'c')
putchar(__crt_va_arg(arg, char));
else if ((*format) == 's')
{
char *ap = __crt_va_arg(arg, char *);
assert(ap);
while (*ap != '\0')
putchar(*(ap++));
}
}
else
{
putchar(*format);
} format++;
}
__crt_va_end(arg);
return 1;
}