变参函数
变参函数其实并不罕见,在初学者的的 helloworld 程序中就用到了变参函数 printf() ,在之后的学习当中,scanf() 又是另一个常见的变参函数。
这些函数有以下共同特点:
- 可以输入一个到任意多个参数
- 从第二个参数开始,参数的类型不固定
- 可以从第一个参数推测应该输入的参数类型及个数
实现原理
在函数调用过程中,相应内存的栈空间会增长,这时函数参数会被压到栈中。在普通函数执行时,函数根据参数列表得知栈内的参数类型以及个数,计算出相应参数的地址 和 占用空间大小,从而从栈中获取各个形参变量。
变参函数也要知道实际传入参数的类型以及个数,通过变参列表的起始位置,计算出所有参数的地址以及长度,这样成功读取到所有传入的参数。
C语言的变参函数需要传入至少一个在变参列表之外的固定参数,而这个固定参数所起到的作用就是 告诉函数变参列表的类型及个数 和 定位变参列表的位置。
实现方法
- 包含 stdarg.h 头文件
- 参数列表为固定参数+可变参数的形式,可变参数在声明和定义中用省略号,即三个点号(…)表示。
- 函数定义中创建一个 va_list 类型变量。
- 使用最后一个固定参数和 va_start 宏来初始化 va_list 变量为一个参数列表。
- 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
- 最后,使用宏 va_end 来清理赋予 va_list 变量的内存。
代码示例
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
int my_printf(const char *format, ...)
{
int length = strlen(format);
va_list valist;//创建valist变量
va_start(valist,format);//使用...前一个参数初始化valist
for (int i = 0; i < length-1; i++){
if (format[i] == '%'){
switch (format[i+1]){
case 'c':
printf("char: %c\n", va_arg(valist,int));//char 会被转换成 int 类型来存储
break;
case 'd':
printf("int: %d\n", va_arg(valist,int));
break;
case 'f':
printf("float: %f\n",va_arg(valist,double));//float 会被转换成 double 类型来存储
break;
case 's':
printf("char*: %s\n", va_arg(valist,char*));
break;
case 'l':
switch(format[i+2]){
case 'f':
printf("double: %lf\n", va_arg(valist,double));
break;
case 'd':
printf("long: %ld\n", va_arg(valist,long));
break;
}
break;
}
}
}
va_end(valist);//清理为valist开辟的内存
}
int main()
{
//创建变量
int d = 12850;
char c = 'g';
double lf = 45854.8255;
float f = 546.618;
char str[] = "hello world !";
long ld = 1564323;
//调用自定义的my_print函数,测试是否能正确获取所有参数
//参数的顺序和数量要严格对应
my_printf("%d %f %c %ld %lf %s", d, f, c, ld, lf, str);
return 0;
}
输出结果
int: 12850
float: 546.617981
char: g
long: 1564323
double: 45854.825500
char*: hello world !