C++11(四)可变参数模板

一、模板的可变参数

C++11的新特性可变参数模板能够让您创建可以接受可变参数数量的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

C++11中提供了一个用省略号...表示的元运算符,让你能够声明表示模板参数包的标识符,模板参数包实际上是一个类型列表。

下面就是一个基本可变参数的函数模板:

template <class ...Args>
void ShowList(Args... args)
{}

普通的模板函数:

template<class T>
void showlist(T value)
{}

Args是一个模板参数包args是一个函数形参参数包
与其他参数名一样,可以将这些参数包的名称指定为任何符合C++标识符规则的名称,Args与T的区别在于,T与一种类型匹配,而Args与任意数量(包括0)的类型匹配。
更准确的说,函数参数包args包含的值列表与模板参数包Args包含的类型列表匹配——无论是从数量还是类型上!
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

template <class ...Args>
void ShowList(Args... args)
{
	// 获取、解析可变参数
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

并不支持这样使用:

template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(Args) << endl;
	cout << sizeof...(args) << endl;
	for (int i = 0; i < sizeof...(args); i++)
	{
		// 无法编译,编译器无法解析
		cout << args[i] << " ";
	}
}

下标索引的方式不能用,如何获取参数包里面的数据呢?

二、展开参数包

可以将省略号放在函数参数包名字的右边,将参数包展开:

template<class T>
void showlist(Args...args)
{
	showlist(args...);
}

但是这样调用存在缺陷,假如有这样的调用:

showlist(6,'L',0.6);

这将把6、‘L’、0.6 封装到args中,在该函数内部,下面的调用:

showlist(args...);

将展开成这样:

showlist(5,'L',0.5);

该函数调用与原始函数调用相同,因此他将使用相同的参数不断的调用自己,导致无限递归。

三、递归函数方式展开参数包

要求:解析并打印每个参数包中的类型及其值
增加一个模板参数,我们还是要将函数参数包展开,对列表的第一项进行处理,再将余下的内容传递给递归调用处理,以此类推,直到列表为空。与常规的递归一样,确保递归的终止很重要。

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl << endl;
}

// 展开函数
template <class T,class ...Args>
void ShowList(T val,Args... args)
{
	cout << typeid(val).name() <<":"<<val << endl;
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

在这里插入图片描述
对于上述定义,第一个实参决定了Tvalue的类型,而其他实参决定了Argsargs的值,这让函数能够对value进行处理,然后可以递归调用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)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

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);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

在这里插入图片描述

五、C++11STL库新增函数接口

STL中push_back接口
在这里插入图片描述
在这里插入图片描述
STL中emplace_back接口
在这里插入图片描述

int main()
{
	std::list< std::pair<int, char> > mylist;
	mylist.push_back(make_pair(1, 'A'));
	mylist.emplace_back(make_pair(1, 'A'));
	mylist.emplace_back(1, 'A');

	for (auto e : mylist)
		cout << e.first << ":" << e.second << endl;
}

对比push_back和emplace_back接口
下面我们试一下带有拷贝构造和移动构造的sjj::string,我们会发现其实差别也不到,emplace_back是直接构造了,push_back是先构造,再移动构造,其实也还好。

int main()
{
	std::list< std::pair<int, sjj::string> > mylist;
	mylist.emplace_back(10, "sort");
	mylist.emplace_back(make_pair(20, "sort"));

	cout << endl << endl;
	mylist.push_back(make_pair(30, "sort"));
	mylist.push_back({ 40, "sort" });

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值