变参函数
引言
最近在把一些代码封装成函数的时候,遇到一些参数个数可变的情况,为此想到是不是使用变参函数来解决这个问题。但是关于变参函数却一开始就没掌握。今天花时间来看一下这个部分,并且讲总结记录下来。
这里我是参考的其他大神的方式先进行了直接输出变参的探索过程,如果不想看此部分,可以通过目录直接跳转到<变参函数>章节进行查看。
我们第一次见到变参函数应该是在C语言的scanf和printf上,那么我们可以由此引入来学习变参函数的用法。
不看函数内部的函数体,我们来看printf函数的函数原型
int printf(const char *format, ...);
我们在平时使用的时候,可以发现我们的参数1:const char *format填入的是我们想要显示的内容。然后后面跟着的就是的"format"中变量的指引,也就是我们看到函数原型中"…"的部分,这个部分没有指定具体的参数类型,也没有指定特定的参数数量。因此我们称为变参。
用我们的方法探索
确定固定参数
我们仿照printf定义一个子函数然后在主函数中调用然后我们输出一下我们看到的固定参数。代码及运行结果如下:
int fun(const char *format,...)
{
printf("arg1=%s\n",format);
return 0;
}
int main(void)
{
fun("固定参数");
return 0;
}
手动确定变参
我们仿照printf的使用,来看看第二个参数是如何使用的。在我们的子函数中输出一下第二个参数,然后我们调用的时候添加第二个参数。
我们知道函数在传递参数的时候,参数是从左到右依次入栈,也就是说每个参数的地址是累加的,那么输出第二个参数我们可以考虑用一个指针偏移来获取后面变参的地址。代码及运行结果如下:
int fun(const char *format,...)
{
int arg2;
char *p = (char *)&format;
printf("arg1=%s\n",format);
p += sizeof(char *);
arg2 = (*(int *)p);
printf("arg2=%d\n",arg2);
return 0;
}
int main(void)
{
fun("固定参数",111);
return 0;
}
增加变参个数
上面我们发现,增加了一个整形的参数是可以正确输出的,现在我们继续探索,增加参数的个数并且使用不同类型的参数来试着输出。c
int fun(const char *format,...)
{
int arg2;
char arg3;
float arg4;
char *p = (char *)&format;
printf("arg1=%s\n",format);
p += sizeof(char *);
arg2 = (*(int *)p);
printf("arg2=%d\n",arg2);
p += sizeof(int);
arg3 = (*(char *)p);
printf("arg2=%c\n",arg3);
p += sizeof(char);
arg4 = (*(float *)p);
printf("arg4=%f\n",arg4);
return 0;
}
int main(void)
{
fun("固定参数",111,'A',5.2);
return 0;
}
寻找问题
可以发现上面输出不同类型的参数的时候出现问题,这样的问题是因为,第一个变参是"int"类型,占了4个字节。而第二个变参是"char"类型。第二个变参实际上也是四个字节,但是我们只用了一个"char"类型,用了一个字节,那么后面三个字节全部都是空的垃圾值。假如我们在输出"float"类型之前,再多跳转三个字节的地址,则可以成功输出。
虽然大佬是这么说的,但是不知道为什么我试不出来,我输出还是错的。不过我们的目的是来学习变参函数的,现在这个问题暂时搁置吧。
变参函数
变参函数常用以下几个宏,这些宏定义在<stdarg.h>头文件中
- va_list:用来保存后面几个函数中所需的信息,看名字也可以明白,与变参列表息息相关。
- void va_start( va_list arg_ptr, prev_param ):变参起始宏,这个函数将会初始化va_list定义的变量,根据参数我们能理解,就是指定变参开始的位置。
- type va_arg( va_list arg_ptr, type ):获取变参的宏,其中指定参数类型,得到该类型的参数内容。每次调用都会修改va_list定义的变量,然后会将其指向参数列表的下一个参数。
- void va_end( va_list arg_ptr ):结束变参宏,调用可以结束整个变参过程。
实现变参获取通过以下步骤即可。
- 首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针。
- 然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
- 然后用va_arg返回可变的参数,并赋值给整数j。va_arg的第二个参数是你要返回的参数的类型,这里是int型。
- 最后用va_end宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用va_arg获取各个参数。
下面举一个例子来实现变参函数:
void fun(int num,...)
{
va_list arc;
va_start(arc,num);
printf("%d\n",va_arg(arc,int));
printf("%c\n",va_arg(arc,char));
va_end(arc);
}
void main(void)
{
fun(2,1,'c');
return 0;
}