
➤ 可变参数
(1)可变参数函数,以符号 “...” 作为可变参数函数的标识,可变参数一定是参数列表中最后一个位置,参数个数一般以参数列表首个参数传入,否则不知道什么时候该结尾,当然也可通过其它方式,比如 printf 函数中搜索%的个数来确定参数个数。大致原理为:从参数首地址开始,因为必须指定每个参数类型,所以可由此计算出地址运算的范围,返回正确的对应类型的地址。
#include <stdarg.h>
void func1(unsigned int argsNum, ...)
{
va_list args;
va_start(args, argsNum);
// 参数列表中的参数都是同一类型
for (unsigned int idx = 0; idx < argsNum; ++idx)
{
cout << va_arg(args, int) << endl;
}
// 参数列表中的参数,也可以是不同类型,不过就需要一一指定了
cout << va_arg(args, int) << endl;
cout << va_arg(args, const char*) << endl;
cout << va_arg(args, float) << endl;
va_end(args);
}
(2)可变参数宏,和可变参数函数类似,也采用符号 ... ,作为宏定义左侧的可变参数标识,而宏定义右侧则采用内置宏 __VA_ARGS__,可理解为将 ... 所传入的参数列表,替换到 __VA_ARGS__ 的位置。
#define MyPrint(...) printf(__VA_ARGS__)
int main()
{
// 输出:Print: Hello world ~ 666
MyPrint("Print: %s ~ %d", "Hello world", 666);
return 0;
}
(3)可变参数模板,在函数模板的基础上,进一步定义可变参数。相比于可变参数函数,在可变参数模板可以通过特定的运算符 sizeof... 获取参数列表中参数的个数,此运算符只能在可变参数模板所定义的函数中使用。初始化参数列表常用于容器的初始化,如vector大括号初始化。这里可变参数用参数包来描述,解参数包有一般有两种方式:
① 递归法:标准的解可变参数包的方法,需要定义一个接受0个参数的终止函数。需要注意的是,在递归过程中,使用 sizeof... 运算符得到的是当前调用时参数的数量,每执行以此递归,将消耗一个参数。
// 同名的无参数终止函数,在递归结束后调用
void func()
{
cout << "Analysis done.\n" << endl;
}
template<typename FirstArg, typename... MultiArgs>
void func(FirstArg firstArg, MultiArgs... args)
{
// 得到此次递归调用时,剩余的参数数量
unsigned int leftArgsNum = sizeof...(args);
// 递归依次输出:
// Cur arg: 111 Left args count:3
// Cur arg: 222 Left args count:2
// Cur arg: 333 Left args count:1
// Cur arg: 444 Left args count:0
// Analysis done.
cout << "Cur arg: " << firstArg << "\tLeft args count:" << leftArgsNum << endl;
func(args...);
}
int main()
{
func(111, 222, 333, 444);
} ② 初始化列表方法:C++11中的型特性,初始化参数列表,std::initializer_list,结合逗号表达式,可非递归的解参数包。
template<typename... MultiArgs>
void func(MultiArgs... args)
{
// 得到参数的数量
unsigned int argsNum = sizeof...(args);
// 用一个容器保存参数列表
vector<int> vecOb;
// 这个 666 可能是为了保证逗号表达式的语法,具体用意不明,这里可以是任何(或可隐式转换为)int 类型的数据
std::initializer_list<int>{ (vecOb.push_back(args), 666)... };
for (vector<int>::iterator itr = vecOb.begin(); itr != vecOb.end(); ++itr)
cout << *itr << ','; // 输出:111,222,333,444
cout << endl;
// 因为vector本身也可以用初始化列表来初始化,所以可以直接获取参数列表
vector<int> vecArgs{ args... };
for (vector<int>::iterator itr = vecArgs.begin(); itr != vecArgs.end(); ++itr)
cout << *itr << ','; // 输出:111,222,333,444
}
int main()
{
func(111, 222, 333, 444);
} 特别注意:应该尽可能的避免使用可变参数,如果函数需要多个参数,优先考虑重载。