C++编程中实现可变参数函数有多种途径,本文介绍一种最常见的实现途径,即可变参数宏方法:形参生命为省略符,函数实现时用参数列表宏访问参数。
1. 可变参数宏实现变参函数
可变参数宏实现可分为以下几个步骤:
-
函数形参原型中给出省略符; 函数实现中声明一个va_list可变参数列表变量; 开始初始化构造va_list变量; 访问变参列表; 完成清理工作;
上述步骤的实现需要使用到四个宏:
-
va_list void va_start(va_list ap, last_arg) type va_arg (va_list ap, type) void va_end(va_list ap)
va_list 是在C语言中解决变参问题的一组宏
void va_start(va_list ap, last_arg)
ap :是一个 va_list 类型的对象,它用来存储通过 va_arg 获取额外参数时所必需的信息。
last_arg :是最后一个传递给函数的已知的固定参数,即省略号之前的参数。
宏定义:type va_arg (va_list ap, type)
该宏用于变参数函数调用过程中,type是当前参数类型,调用该宏后,ap指向变参数列表中的下一个参数,返回ap指向的参数值,是一个类型为type的表达式。 ap是arg_ptr参数指针之意。
void va_end(va_list ap)
允许使用了 va_start 宏的带有可变参数的函数返回。如果在从函数返回之前没有调用 va_end,则结果为未定义。
这些宏在头文件stdarg.h中声明定义。因此使用时需要包含该头文件。
下面给出用法示例:
#include <stdarg.h> //可变参数函数sum(),求任意个数整数的和。 //Step1: 函数形参原型中给出省略符 int Sum(int count, ...); int Sum(int count, ...) { //Step2: 函数实现中声明一个va_list可变参数列表变量; va_list ap; //Step3: 开始初始化构造va_list变量, 第二个参数为最后一个确定的形参 va_start(ap, count); int sum = 0; for(int i = 0; i < count; i++) { //读取可变参数,的二个参数为可变参数的类型 sum += va_arg(ap, int); } //清理工作 va_end(ap); return sum; }
实际中使用可变参数宏实现C++可变参数函数编程,还要注意一下几点:
函数原型中省略号必须在参数列表的末尾:也就是说,在函数原型中参数列表省略号的右边不能再出现确定参数; 试用完成是用va_end做清理工作步骤不可缺少,否则可能导致内存或资源泄漏; va_list在一次访问中不能后退,但可以多次构造va_list多次访问;2. 更安全的可变参数函数实现方法
对于上面示例代码中count传进的实参如果与后面...省略符对应的实际参数数量不一致时,可能导致函数风险。这一切完全依赖运行时的具体情况而定,很不安全。
另一种更安全的可变参数宏实现方法是利用C++的 attribute ((format()))特性来辅助可变参数的检查。
最常见的形式是有如下两个:
__attribute__((format(printf, m, n))) __attribute__((format(scanf, m, n)))
其中参数m与n的含义为:
m:第几个参数为格式化字符串(format string);
n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几;
attributeformat属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。format属性告诉编译器,按照printf, scanf等标准C函数参数格式规则对该函数的参数进行检查。这在我们自己封装调试信息的接口时非常的有用。
format的语法格式为:
format (archetype, string-index, first-to-check)
其中,“archetype”指定是哪种风格;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。
下面给出2个示例:
一般函数:
为自己定义的一个带有可变参数的函数,其功能类似于printf:
extern void myprint(const char *format,...) attribute ((format(printf,1,2))); //m=1;n=2 extern void myprint(int l,const char *format,...) attribute ((format(printf,2,3))); //m=2;n=3
类成员函数
需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,例如:
extern void myprint(int l,const char *format,...) attribute ((format(printf, 3,4 )));
其原因是,类成员函数的第一个参数实际上一个隐身的this指针。