文章目录
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
下面就是一个基本可变参数的函数模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
如何获取可变参数的个数?
利用sizeof…(可变参数包名称)计算包的大小
#include<iostream>
using namespace std;
template <class ...Args>//可变参数模板
void ShowList(Args... args)
{cout << sizeof...(args) << endl;//这里用sizeof
}
int main()
{ShowList();ShowList('x');ShowList('x', 'y');ShowList('x', 1);return 0;
}
运行结果:
如何解析出可变参数包?
递归思维推导——代码如下:
#include<string>
#include<iostream>
using namespace std;
//递归终止函数
void ShowList()
{cout << endl;
}
template <class T, class ...Args>//可变参数模板
void ShowList(const T& val, Args... args)
{cout << val << " ";ShowList(args...);
}
int main()
{ShowList();ShowList('x');ShowList('x', 'y');ShowList('x', 1);ShowList(1, 'A', std::string("sort"));return 0;
}
运行结果:
解析:
第一个ShowList函数没有传参数,而我们又自己实现了ShowList(),所以编译器直接去调用这个函数,不会走模板,所以什么都没有打印;第二个,函数只传递了char字符,通过可变参数模板的推导,T为char类型,而可变参数包为0,注意可变参数包可以包含了0到N(N>=0)个模版参数,所以只打印了’x’,打印之后的下一句代码又递归调用了自己,由于这时编译器推导出来的可变参数为0,所以会去调用我们自己实现的无参的ShowList函数,打印换行;然后我们再以最后一句代码为例,第一层函数,T为int类型,val为1,打印1,可变参数包的大小为2,,包含’A’,“sort”,然后递归调用自己来到第2层,T为char,val为’A’,可变参数包大小为1,包含"sort",打印出’A’后,递归调用自己,打印出"sort",完毕之后,可变参数包的大小这就为0了,最后调用我们自己实现的无参函数打印换行。over!
但是我们看到STL中的函数用到可变参数包,参数是这样写的:
那是因为。。。不一样的写法罢了,看看下面的代码,你们应该秒懂:
template <class ...Args>//可变参数模板
void ShowList(Args... args)//多来一层函数接口
{_ShowList(args...);
}
逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,只是逗号表达式的结果是最后一个值,即0。也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
#include<string>
#include<iostream>
using namespace std;template<class T>
void PrintArg(T t)//不是递归终止函数,不存在递归,是一个处理参数包中每一个参数的函数
{cout << t << " ";
}template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args),0)... };cout << endl;
}int main()
{ShowList(1, 'A', string("sort"));return 0;
}
STL容器中的empalce相关接口函数:
vector/emplace_back
list/emplace_back
template <class... Args>
void emplace_back (Args&&... args);
首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用(注意模板中的是万能引用,不叫右值引用)。那么相对insert和emplace系列接口的优势到底在哪里呢?
int main()
{std::list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
下面我们试一下带有拷贝构造和移动构造的string,我们会发现其实差别也不到,emplace_back是直接构造,push_back是先构造,再移动构造。
int main()
{std::list< std::pair<int, zxn::string> > mylist;mylist.emplace_back(10, "sort");mylist.emplace_back(make_pair(20, "sort"));mylist.push_back(make_pair(30, "sort"));mylist.push_back({ 40, "sort"});return 0;
}
我们再插入日期类试试: