变长参数的函数,即参数个数可变、参数类型不定的函数。设计一个参数个数可变、参数类型不定的函数是可能的,最常见的例子是printf函数、scanf函数和高级语言的Format函数。在C/C++中,为了通知编译器函数的参数个数和类型可变(即是不定的、未知的),就必须以三个点结束该函数的声明。例如:
//printf函数的声明
int printf(const char * _Format, ...);
//scanf函数声明
int scanf(const char * _Format, ...);
//自定义变长参数函数func的声明
int func(int a, int b, ...);
上面func函数的声明指出该函数至少有两个整型参数和紧随其后的0个或多个类型未知的参数。在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。
变长参数函数的实现
含有变长参数的函数是怎么实现的呢?变长参数函数的实现其实关键在于怎么使用参数,指定了的参数好说,直接使用指定的参数名称访问,但未指定的参数呢?我们知道函数调用过程中参数传递是通过栈来实现的,一般调用都是从右至左的顺序压参数入栈,因此参数与参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数的内容。对于变长参数函数,结合一定的条件,我们可以根据最后一个指定参数获取之后的省略参数内容。如,对于函数func,我们知道了参数b的地址及类型,就可知道第一个可变参数的栈地址(如果有的话),如果知道第一个可变参数的类型,就可知道第一个可变参数的内容和第二个可变参数的地址(如果有的话)。以此类推,可以实现对可变参数函数的所有参数的访问。
那么,要怎么指定上诉的“一定的条件”呢?最简单的方法就像printf等函数一样,使用格式化占位符。分析格式化字符串参数,通过事先定义好的格式化占位符可知可变参数的类型及个数,从而获取各个参数内容。一般对于可变参数类型相同的函数也可直接在强制参数中指定可变参数的个数和类型,这样也能获取各个参数的内容。
无论哪种,都涉及对栈地址偏移的操作。结合栈存储模式和系统数据类型的字长,我们可根据可变参数的类型很容易得到栈地址的偏移量。
变长参数函数的简单运用:
// va_arg_test.cpp : 定义控制台应用程序的入口点。
//
//访问可变参数流程
//va_list args; //定义一个可变参数列表
//va_start(args,arg); //初始化args指向强制参数arg的下一个参数
//va_arg(args,type); //获取当前参数内容并将args指向下一个参数
//... //循环获取所有可变参数内容
//va_end(args); //释放args
#include "stdafx.h"
#include <stdarg.h>
#include <string>
#include <vector>
/// @brief 利用变长参数函数进行求和运算
/// @param[in] n : 指定后面有多少可变参数
/// @remark 第一个参数为强制参数
/// @return 得到求和结果
double sum(unsigned int n, ...)
{
double sum = 0;
va_list args = NULL;//定义一个可变参数列表
va_start(args, n);//初始化args指向强制参数arg的下一个参数
while(n > 0)
{
//通过va_arg(args, double)依次获取参数的值
sum += va_arg(args, double);
n--;
}
va_end(args);//释放args
return sum;
}
/// @brief 利用变长参数函数格式化字符串
/// @param[in] format : 类似printf中第一个参数
/// @remark
/// @return 格式化后的字符串
std::string format(const char* format, ...)
{
std::string var_str;
va_list ap;
va_start(ap, format);
int len = _vscprintf(format, ap);
if (len > 0)
{
std::vector<char> buf(len + 1);
vsprintf(&buf.front(), format, ap);
var_str.assign(buf.begin(), buf.end() - 1);
}
va_end(ap);
return var_str;
}
//
int _tmain(int argc, _TCHAR* argv[])
{
//求和
double cnt = sum(6, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6);
printf("sum: %f\n", cnt);
//格式化字符串
std::string szTmp = format("%s,%s,%s", "this", "is", "kandy");
printf("%s \n", szTmp.c_str());
system("pause");
return 0;
}
对于可变参数函数的调用有一点需要注意:实际的可变参数的个数必须比前面强制参数中指定的个数要多,或者不小于。也即后续参数多一点不要紧,但不能少!如果少了则会访问到函数参数以外的堆栈区域,这可能会把程序搞崩掉。前面强制参数中指定的类型和后面实际参数的类型不匹配也有可能造成程序崩溃。