C语言中的可变参数列表
首先写一段简单的代码用来理解可变参数列表
分别使用可变参数,实现函数,求函数参数的平均值和最大值。
#include<stdio.h> #include<stdarg.h> int average(int n, ...) { va_list arg; int i = 0; int sum = 0; va_start(arg, n);//初始化arg为未知参数列表的第一个参数的地址 for (i = 0; i < n; i++) { sum += va_arg(arg, int); } return sum / n; va_end(arg); } int max(int n, ...) { va_list arg; int i = 0; int max = 0; va_start(arg, n); for (i = 0; i < n; i++) { int val = va_arg(arg, int); if (val>max) { max = val; } } va_end(arg); return max; } int main() { int a = 1; int b = 2; int c = 3; int avg1 = average(2, a, c); int avg2 = average(3, a,b, c); int max1 = max(2, a, c); int max2 = max(3, a, b, c); printf("avg1=%d\n", avg1); printf("avg2=%d\n", avg2); printf("max1=%d\n", max1); printf("max2=%d\n", max2); return 0; }
可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中,在这个头文件中声明了 一个类型va_list和三个宏va_start、va_arg、va_end配合使用,访问参数的值。
在vs中我们可以转到定义处查看各个类型和宏具体是怎样实现的
1.va_list arg; 的意思是定义了一个arg变量,这个arg用于访问参数列表未确定的部分。相当于 一个声明。
typedef char * va_list
#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)) )
NTSIZEOF(n);//当n的大小为1或2或3字节时,INSIZEOF(n)=4 //当n的大小为5或6或7个字节时,INSIZEOF(n)=8 //...
即上面的代码中va_start(arg, n)就可以替换为:
arg=(char *)&n + 4;//把n的地址取出并强制类型转换为char*类型 //再给n的地址加4并让arg指向将这个地址 //即让arg指向参数部分的第一个参数
3. va_arg(arg,int)这个宏的意思时按第二个参数类型读取可变参数列表。va_list定义的变量arg和列表中下一个参数的类型。特别注意va_arg这个宏会返回这个参数的值,并且指向下一个可变参数。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
将va_arg(arg, int)进行替换:
sum+=(*(int *)((arg+=4)-4));//先将arg加4的值赋给arg //使得arg指向的位置向后挪动4个字节 //再将arg-4 使得表达式结果所产生的地址指向原先的位置 //将所得到的结果强制类型转换为int*类型并进行解引用
这段代码的作用:取出当前位置参数并为下一次获取参数做好准备。
4. va_end(arg)宏的意思是使指针置为无效,相当于p=nuLL.
可变参数的局限性
1.我们至少必须知道一个明确的参数,当然如果明确参数的有多个,选择最右的已知参数,因为它距离可变参数变量最近。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是如果你想开始就访问参数列表中间的参数,那是不行的。
2.宏是无法判断数量的,所有需要把长度传过去。不然可能不会终止的向后,导致报错。
3.宏是无法判断类型的,你定义已什么类型读,就会已什么类型读取。所以,可变参数函数在调用时非明确的实参应该是同一类型的。
4.如果在va_arg中指定了错误的类型,那么后果是不可预测的。
下面模拟实现printf函数,可完成下面的功能//
//能完成下面函数的调用。
//print("s ccc d.\n","hello",'b','i','t',100);
//函数原型:
//print(char *format, ...)
//
#include<stdio.h> #include<stdarg.h> #include<string.h> void print_int(int n)//递归打印,注意这里的(args,int)是int,整形,不是全都charcharchar { if (n > 9) { print_int(n / 10); } putchar(n %10+'0'); } void my_printf(const char *format, ...) { va_list arg; va_start(arg, format); while(*format) { switch (*format) { case 's': { char *str = va_arg(arg, char*);//因为是字符串,所以要用char*解引用 while (*str) { putchar(*str); str++; } } break; case 'c': { char ch = va_arg(arg, char);//单个字符打印 putchar(ch); } break; case 'd': { int ret = va_arg(arg, int); printf_int(ret); } break; defult: putchar(*format); break; } format++; } } int main() { my_printf("s ccc.\n", "hello", 'b', 'i', 't'); system("pause"); return 0; }