- 如何实现像printf()一样的可变参数函数
一般用到以下几个宏
va_list arg_ptr//此处为类型定义 不是宏
va_start(va_list arg_ptr, type);
type va_arg(va_list, type);
void va_end(va_list);
va这里指variavle arguments,这些宏定义于<stdarg.h>头文件下,下面实现一个简单函数打印所有参数的值:
#include <stdarg.h>
#include <stdio.h>
void myVarArgFun(int i...);
int main()
{
myVarArgFun(10);
myVarArgFun(10, 20);
myVarArgFun(10, 20, 30);
getchar();
}
void myVarArgFun(int i...)
{
va_list(args);
va_start(args, i);
printf(" %d ", i);
int j = va_arg(args, int);
while (j)
{
printf(" %d ", j);
j = va_arg(args, int);
}
va_end(args);
printf("\n");
}````
由以上可以看出,实现一个可变参数函数应有一下步骤:
+ 首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
+ 然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
+ 然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
+ 最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.
***
**可变参数在编译器中的处理**
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面VC++中stdarg.h里x86平台的宏定义摘录如下(’/’号表示折行):
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) ) #define va_arg(ap,t) /
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
define va_end(ap) ( ap = (va_list)0 )
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的
***
**可变参数在编程中要注意的问题**
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型.有人会问:那 么printf中不是实现了智能识别参数吗?那是因为函数printf是从固定参数format字符串来分析出参数的类型,再调用va_arg的来获取可 变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
***
**小结:**
可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们 无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.