C语言中类似printf()函数这样支持可变参数的功能是如何实现的?这要归功于va_start,va_arg,va_end等几个宏的支持。
这个几个宏定义如下:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是将类型n的大小向上取成4的倍数,如n为char型的话结果即为4
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是将v的地址重新解释成char*型
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
#ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char * va_list; // vs2015中此句高亮
#endif /* _M_CEE_PURE */
#define _VA_LIST_DEFINED
#endif
// 以上宏定义出现在vadef.h,通过stdio.h即可使用
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
// 以上宏定义出现在stdarg.h中,若要使用则需加上 #include <stdarg.h>
具体使用例子:
#include <stdio.h>
#include <string.h>
#include <stdarg.h> // 必需头文件
int demo(char *, ...);
int main()
{
demo("DEMO", "This", "is", "a", 1, 2.0);
return 0;
}
int demo(char *msg, ...)
{
va_list argp; // 必需,其实就是第一个指针,会依次指向各参数。
int argno = 0;
char *para;
va_start(argp, msg); // 使argp指向第二个参数,即msg之后的参数
para = va_arg(argp, char*); // 两个效果:1,先把argp当前指向的参数传给para;2、然后使argp指向下一个参数。注意va_arg()的第二个参数类型要与实际参数类型一致。
printf("Parameter 2 is: %s\n", para);
para = va_arg(argp, char*); // 第三个参数是char*类型,所以va_arg()第二个参数也必须是char*类型。
printf("Parameter 3 is: %s\n", para);
para = va_arg(argp, char*);
printf("Parameter 4 is: %s\n", para);
int arg1 = va_arg(argp, int);// 第五个参数是int类型,所以va_arg()第二个参数也必须是int类型。
printf("Parameter 5 is: %d\n", arg1);
float arg2 = va_arg(argp, double);// 第六个参数是float类型,va_arg()第二个参数必需是double类型,暂时不晓得为什么
printf("Parameter 6 is: %f\n", arg2);
va_end(argp); // 将argp置为空
return 0;
}
输出:
Parameter 2 is: This
Parameter 3 is: is
Parameter 4 is: a
Parameter 5 is: 1
Parameter 6 is: 2.000000
总结:
-
需要包含头文件<stdarg.h>,要用到va_list, va_start, va_arg, va_end这几个宏(注意,这些都是宏定义哦);
-
上述几个宏要保证使用顺序;
-
va_arg()第二个参数类型一定要与当前指向的参数类型一致(注意搞清楚当前指向的是哪个参数);
-
使用技巧上,可变参数函数的第一个参数可以用来记录函数调用时可变参数的个数。比如demo()里第一个参数msg可以声明为int类型,如果后面有5个可变参数,就赋值为5,这样方便使用循环来读取参数。实际调用效果如下:
int demo(int msg, ...);
int main()
{
demo(5, "This", "is", "a", 1, 2.0);
return 0;
}
//......
5. printf()函数是先解析第一个格式字符串,来确定有几个参数,分别是什么类型。
参考资料:
-
http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 此文章中源码有问题,注意看评论