在传统的C和C++中,函数形参和实参的个数不仅需要保持一致,而且需要显式定义出来,是固定的。在C++11中增加了可变参数这种特性,这篇文章就学习下C++11的可变参数。
C++11的可变形参提供了两种使用场景:
1.个数未知,但所有类型相同
2.类型可能不同,可变参数模板
对于这种场景,可以使用initializer_list的标准库类型,该类型被定义在头文件#include
initializer_list提供了一些操作,如下图:
参考:C++ 11 新特性:可变形参_freshman94的博客-CSDN博客_c++可变形参
有一点需要注意的是initializer_list对象中的元素永远是常量值,无法改变。这个在使用上会产生一些制约,只能提供遍历的操作。
这里引用一个代码实例,很清楚就知道initializer_list的使用方法了:
参考:C++ 11 新特性:可变形参_freshman94的博客-CSDN博客_c++可变形参
对于这种情况,比较常用的是可变参数模板。本质上还是一个模板函数,相对于普通模板提供更多泛化。
参数包指可变数目参数,参数包分为模板参数包,函数参数包
写个简单模板(这个模板关键字写了class,typename也行):
template<class... T> // T是模板参数包
void function(T ... args) // args是函数参数包
{
}
模板中的省略号…代表一个包含0到n的任意参数包,其中的任意代表任意数目和任意类型。…和参数的位置关系也有含义,…在参数右侧代表一组实参,…参数左侧代表可变参数。引用一个例子做说明:
这里的1和2都是表示可变参数,都在参数左侧。而3代表实参,放在参数右侧。
参考:C++11新标准-1.可变模板参数(variadic templates) - 简书 (jianshu.com)
上面这个例子其实有几个隐藏的知识点,首先是template
template<class T1, class ... T2>
void function(T1 t1, T2 ... t2) {
}
上面这个就代表至少要有一个T1类型的参数,和0-n个T2模板包类型的参数。
其次就是Print函数中本身是一个递归调用,这就涉及到可变参数的两种调用方式:递归调用、非递归调用。
先看看递归调用,第一步调用处理包的第一个实参,然后使用剩下的实参调用自身模板,直到最后一个(也叫边界条件)。所以在递归调用的使用中是一定要设置边界条件的,看下面这个例子就明白了:
#include <iostream>
void print() {
std::cout << “hello world!” << std::endl;
}
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args) {
std::cout << firstArg << " " << sizeof...(args) << std::endl; // sizeof ... args代表获取参数个数
print(args...);
}
template <typename... Types>
void print(const Types&... args) {
std::cout << "print(...)" << std::endl;
}
int main(int argc, char *argv[]) {
print(2, "hello", 1);
return 0;
}
这个代码的执行结果是:
2 2
hello 1
1 0
hello world
这个代码有几点需要注意:
1.两个模板函数重载,为什么调用第一个?
这个是因为当较泛化和较特化模板函数同时存在时,回优先选择较特化的。第一个代表至少一个,是较特化。
2.边界条件的存在
边界条件是一定要存在的(不存在编译过不去),一般以同名函数的无参数版本存在。
参考:【C++】C++11可变参数模板(函数模板、类模板)_Yngz_Miao的博客-CSDN博客_c++11可变参数模板
对于非递归调用,就是一次性直接输出了,代码如下:
#include<iostream>
// 形式1:...在左边
template<typename T, typename... Types>
void print1(const T& firstArg, const Types&... args) {
std::cout << "print1" << std::endl;
std::cout << firstArg << std::endl;
(std::cout << ... << args) << std::endl;
}
// 形式2:...在右边
template<typename... Types>
void print2(const Types ... args) {
std::cout << "print2:" << std::endl;
((std::cout << args<< " "), ...) << std::endl;
}
int main() {
print1(2, "hello");
print2(3, "world");
return 0;
}
有的时候可能发生在继承场景下,对子类会有限制,需要和父类保持个数一致比如:
子类BMW继承了Car,Car规定模板两个参数,BMW继承的也不能超过两个或者小于两个,如果是BMW
参考:C++11新特性之五——可变参数模板 (bbsmax.com)
比如printf(“%d%s”, 1, “hello world”);在读取时从右往左读,可以分为三个部分(两个变量一个字符串),以压栈的方式存储。存储的内容是变量的地址,把”%d%s”的指针作为基准A,对%d%s进行解析(从左往右),分成一组一组的,比如%d或者%s。地址指针的大小是固定的,所以A+4B就是第一个参数的地址,A+8B就是第二个依次类推,根据解析出来的个数进行输出,实现可变参数。
因作者水平有限,如有错误之处,请在下方评论区指出,谢谢!