可变形参函数
https://blog.csdn.net/qq_35280514/article/details/51637920
在c++编程中,有时我们需要编写一些在源代码编写阶段无法确定参数个数,有时甚至无法确定参数类型的函数。
因此我们需要一类函数:它们可以在运行时取任意的实参个数并根据实参的个数自动处理不同实参的情形,或者至少可以在运行时指定任意的实参个数。
在C++中实现一个变参函数的方法有三种:
- 第一种方法,将函数形参声明为C++11新标准中的initializer_list标准库类型;
- 第二种方法继承自C语言,形参声明为省略符,函数实现时用参数列表宏访问参数;
- 第三种方法:利用C++泛型特性,声明一个可变参数模板来实现。
重点介绍第一种方法和第三种方法
initializer_list实现可变参函数
实现步骤如下:
- 函数原型中使用实例化initializer_list模板代表可变参数列表;
- 使用迭代器访问initializer_list中的参数;
- 传入实参写在{}之内。
以上步骤中使用到initializer_list。这是C++11新标准中引入的一个标准库类型,与vector等容器一样,initializer_list也支持begin()和end()操作,返回指向首元素的迭代器和尾后迭代器。initializer_list在同名头文件中声明,其实现由编译器支持。
以下代码使用initializer_list实现函数sum。(忽略可能存在的整形溢出)
/* --sum.cpp-- 利用initializer_list模板实现求人一个整形值得和 */
#include <initializer_list>
int sum(initializer_list<int> il); //函数原型用int实例化initializer_list作为形参
int sum(inttializer_list<int> il){
int sum = 0;
for(auto p = il.begin(); p != il.end(); p++) //使用迭代器访问参数
sum += *p;
return sum;
}
使用这种方法需要注意一下几点:
- initializer_list在C++11中才被引入,这意味着在编译时可能需要加上这个选项 -std=c++11 才能成功编译。上述代码中的auto关键字也是C++11的一部分;
- 参数必须放在一组‘{}’(大括号)内,编译器通过大括号来将这组参数转化为initializer_list.大括号的的一组实参与initializer_list形参对应;
- 函数原型initializer_list与普通形参无异。这表明形参列表中可以包含其他类型参数且位置不限,以下函数原型是正确的:
void func(char c, initializer_list<int> il, double d);
- 同一个initializer_list中的参数具有相同的类型。本质上来说initializer_list是一个编译器支持的容器类模板,同其他容器一样,容器中的元素具有相同的类型。
使用这种方法的一个实例是C++11中vector的列表初始化构造函数。
可变参数模板
在介绍这种方法之前需要先介绍两个并不常用的概念:模板参数包和函数参数包。
- 模板参数包是零个或多个类型参数的集合。模板参数列表中,class…或typename…表明其后的类型参数表示一个模板参数包;
- 函数参数包是零个或多个非类型参数的集合。函数形参列表中类型名加省略号表明其后的参数表示一个函数参数包;另外,类型为模板参数包的函数形参是一个函数参数包。
//args是一个模板参数包;rest是一个函数参数包
//args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template<typename T, typename... args>
void foo(const T &t, const args&... rest);
- 与sizeof()运算符类似,sizeof…()运算符用于参数包。sizeof…()将返回参数包中参数个数。
利用可变参数模板实现可变参数函数的步骤如下:
- 编写含有模板参数包和函数参数包的模板函数;
- 函数定义递归调用自己,每一步递归参数包中参数减一;
- 编写处理边界情况(参数包含有零个参数)的模板。
//用来终止递归并答应最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template <typename T>
std::ostream &print(std::ostream &os, const T &t){
return os << t; //包中最后一个元素
}
//包中除最后一个元素之外的其他元素都会调用这个版本的pirnt
template <typename T, typename... Args>
std::ostream &print(std::ostream &os, const T &t, cosnt Args &... rest){
os << t << ","; //打印第一个实参,包中元素减一
return print(os, rest...); //递归调用,打印剩余实参
}
使用这种方法需要注意的是:
1. 必须处理边界情况。且如代码注释所示:应当首先定义处理边界情况的模板。
2. 参数包在参数列表最右侧,参数包只能从左至右展开?
3. 参数包能够实现更加复杂的模板
这种实现方式的根本原理实际上与函数重载是一致的。通过定义模板,让编译器根据实参类型自动生成对应的重载函数。