可变参数函数指的是函数的参数个数可变,参数类型不定的函数。
C++提供了两种主要的方法:
(1)如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型。
(2)如果所有的实参类型不完全相同,可以使用可变参数模板。
另外,还有一种特殊的省略符形参,可以用它传递可变数量的实参,不过这种一般只用于与C函数交互的接口程序。
1、省略符形参
在C和C++中,函数可以用省略符形参 … 表示不定参数部分,省略符形参只能出现在形参列表的最后一个位置。
void func_action(parm_list, ...);
// 典型例子
int printf(const char* format, ...)
2、C之可变参数函数
在C++11之前,使用C语言风格的可变参数函数时,需要引入头文件<cstdio>和<cstdarg>,并使用va_start、va_arg和va_end三个宏来处理可变参数。
代码:
#include <cstdio>
#include <cstdarg>
void printArgs(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
int arg = va_arg(args, int);
printf("%d ", arg);
}
va_end(args);
printf("\n");
}
int main() {
printArgs(3, 1, 2, 3); // 输出:1 2 3
printArgs(0); // 输出:
printArgs(6, 1, 2, 3, 6, 5, 4); // 输出:1 2 3 6 5 4
return 0;
}
结果:
1 2 3
1 2 3 6 5 4
注意:
(1)在使用结束之后,一定要使用va_end进行清理工作;因为可变参的机制类似于动态开辟空间,而var_end就相当于内存回收。
(2)在使用时,需要知道传递参数的类型,以及参数的个数;这就规定,在定义函数的时候至少有一个固定的形参,用于传递函数目前的变参的个数。
(3)在函数的定义中,需要变长参列表在固定参之后,即:void func(int cnt,…);。
(4)该方法不安全,容易出现内存溢出,或是泄露的问题,而且变参列表是顺序的,不能回溯之前的参数,就相当于将变参放入了一个队列中,每执行va_arg(pvar,data_type)一次,pvar指针就往后移动一次,就往后取出一个数据。
3、C++之可变参数模板
一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(表示零个或多个模板参数)和函数参数包(表示零个或多个函数参数)。
当既不知道要处理的实参数目、也不知道它们的类型时,就需要使用可变参数的函数模板了。
在模板参数列表中,class… 或 typename… 指接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。
在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。
// Args是一个模板参数包;rest是一个函数参数包
template <typename T, typename...Args>
void foo(const T &t, const Args&...rest);
可变参数函数模板通常是递归的。第一步调用处理包中的第一个实参,然后用剩余的实参调用自身。为了终止递归,我们还需要定义一个非可变参数的函数模板:
/******************************************************************************
该模板可以在成熟的C++工程中使用。
比如格式化输出数据到文件中等操作。
******************************************************************************/
#include <iostream>
#include <initializer_list>
// 用来终止递归并处理包中最后一个元素
template <typename T>
void print(const T &t)
{
std::cout << t << std::endl;
}
// 包中除了最后一个元素之外的其他元素都会调用这个版本的print
template <typename T, typename...Args>
void print(const T &t, const Args&...rest)
{
std::cout << t << " "; // 打印第一个实参
print(rest...); // 递归调用,打印其他实参
}
// 测试
int main()
{
print("string1", 2, 3.14f, "string2", 42);
return 0;
}
结果:
string1 2 3.14 string2 42
4、C++之initializer_list
如果函数的实参数量未知但是全部实参的类型都相同,可以使用initializer_list类型的形参(C++11新标准)。和vector一样,initializer_list也是一种模板类型。
含有initializer_list形参的函数也可以同时拥有其他形参。另外,如果想给initializer_list形参传递一个实参的序列,必须把序列放在一对花括号内:
#include <iostream>
#include <initializer_list>
#include <string>
std::string func(std::initializer_list<std::string> li, int m)
{
std::cout << m << std::endl;
std::string str("");
for(auto beg=li.begin(); beg!=li.end(); ++beg)
str += *beg;
return str;
}
int main()
{
std::cout << func({"This", " ", "is", " ", "C++"}, 186) << std::endl;
return 0;
}
结果:
186
This is C++