有时
,
可能会碰到这样的情况
.
您希望函数带有可变数量的参数
,
而不是预定义数量
的参数,例如
printf()
和
scanf()
还有
open()
等函数就是可变参数函数,他们的参数
是不确定的。
C
语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根
据具体的需求接受可变数量的参数
函数参数地址问题
在了解可变函数参数之前,我们先来了解一下函数形参地址,我们来看下面一段
程序:
void fun(int a,int b,int c,int d,char e)
{
printf("a=0x%p\n",&a);
printf("b=0x%p\n",&b);
printf("c=0x%p\n",&c);
printf("d=0x%p\n",&d);
printf("e=0x%p\n",&e);
}
int main(int argc, char const *argv[])
{
char a;
fun(1,2,3,4,'a');
return 0;
}
它是用来查看函数形参的地址的,运行:
得到结论:可以发现函数形参的地址是连续的
,
而且从左到右是高地址到低地址存放
的。
但是,系统也不一定完全按这样的规律去执行的,例如参数为:
void fun(int a,double b,int c,int d,int e)
此时运行程序查看参数地址会发现 b
的地址比
c
要低,这种现象的出现可能是由于系
统的某些内存优化处理导致的。
但是.
可变参数的地址是绝对连续的,且地址是从左到右依次变高的,这是和已知参数
的不同之处
可变参数设计(va_list , va_start ,va_arg, va_end )
需要添加的头文件:
#include <stdarg.h>
va_list:
这是一个适用于
va_start()
、
va_arg()
和
va_end()
这三个宏存储信息的类型。
typedef char* va_list;
其实在程序中
va_list
就是一个地址指针,用来指向形参的地址
void va_start(va_list ap, last_arg)
:
这个宏初始化
va_list
变量
,
它
与
va_arg
和
va_end
宏是一起使用的。
last_arg
是最后一个传递给函数的已知的固定
参数
,
即省略号之前的参数。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))
_INTSIZEOF 的作用
是因为形参在内存中的地址是
4
的倍数,所以用来字节对齐,可以看出
va_start
的作用
就是让指针指向传递过来的下一个参数,也就能明白为什么
va_start
的第二个参数要是最后一个已知参数。
type va_arg(va_list ap, type)
:
这个宏检索函数参数列表中类型为
type
的下一个参
数。参数第一个为
va_list
变量,第二个为参数类型
#define va_arg(ap,type) (*(type*)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)))
通过上面的代码我们就不难明白 va_arg
的作用了,就是输出
ap
指向地址参数的值,
同时
ap
指向下一个参数
void va_end(va_list ap)
:
这个宏允许使用了
va_start
宏的带有可变参数的函数返回。
如果在从函数返回之前没有调用
va_end
,则结果为未定义。
#define va_end(ap) (ap = (va_list)0)
va_end
实际就是将
ap
指针指向
null
。
该stdarg.h头文件提供了实现可变参数功能的函数和宏。具体步骤
如下:
1.定义一个函数
,
最后一个参数为省略号
(…),
省略号前面可以设置自定义参数。
2..在函数中创建一个
va_list
类型变量
,
该类型是在
stdarg.h
头文件中定义的。
3.使用
int
参数和
va_start
宏来初始化
va_list
变量为一个参数列表。宏
va_start
是在
stdarg.h 头文件中定义的。
4.使用
va_arg
宏和
va_list
变量来访问参数列表中的每个项。
5.用宏
va_end
来清理赋予
va_list
变量的内存。
举例简单的一个求和函数:
#include <stdio.h>
#include<string.h>
#include<errno.h>
#include<stdarg.h>
int sum(int num,...)
{
va_list valist; //在函数中创建一个 va_list 类型变量
int add1 = 0;
int i;
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
add1 += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return add1;
}
int main()
{
printf("sum = %d\n", sum(4, 2, 3, 4, 5));
printf("sum = %d\n", sum(6, 5, 10, 15,4,7,9));
}
到这里我们就差不多知道怎么去设计可变参数了,同时我们还可以大致知道类似于
printf,scanf
它们是如何实现的,例如我们来分析一下
printf()
的大致实现:
int printf(const char *ptr,...)
{
va_list pr_va;
va_start(pr_va,ptr);
char *p = ptr; //定义指针指向传递进来的字符串
/*遍历 ptr,寻找%,然后根据%后面跟的符号打印替换成相应类型的值*/
while (*p != 0)
{
if(*p == % && *(++p) != 0)
{
switch(*p)
{
case d: /*va_arg(pr_va,int);*/
case s: /*va_arg(pr_va,char*);*/
/*...*/
}
}
p++;
}
}