前言https://m./group/6591056733001482756/?iid=41062847098&app=news_article×tamp=1534862328&group_id=6591056733001482756 更多C/C++学习资料,请私信我“代码”,即可获取 C++11的新特性--可变模板参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模板中只能含固定数量的模版参数,可变模板参数无疑是一个巨大的改进。然而由于可变模板参数比较抽象,使用起来需要一定的技巧,所以它也是C++11中最难理解和掌握的特性之一。虽然掌握可变模板参数有一定难度,但是它却是C++11中最有意思的一个特性,本文希望带领读者由浅入深的认识和掌握这一特性,同时也会通过一些实例来展示可变参数模版的一些用法。 可变模板参数的展开可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“...”。比如我们常常这样声明一个可变模版参数:template 更多C/C++学习资料,请私信我“代码”,即可获取 上面的可变模板参数的定义当中,省略号的作用有两个:
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。 可变模版参数和普通的模版模板语义是一致的,所以可以应用于函数和类,即可变模板参数函数和可变模板参数类,然而,模版函数不支持偏特化,所以可变模板参数函数和可变模板参数类展开可变模版参数的方法还不尽相同,下面我们来分别看看他们展开可变模版参数的方法。 可变模板参数函数一个简单的可变模板参数函数: 更多C/C++学习资料,请私信我“代码”,即可获取 面的例子中, f() 没有传入参数,所以参数包为空,输出的 size 为 0 ,后面两次调用分别传入两个和三个参数,故输出的 size 分别为 2 和 3 。由于可变模版参数的类型和个数是不固定的,所以我们可以传任意类型和个数的参数给函数 f 。这个例子只是简单的将可变模版参数的个数打印出来,如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。下面来看看如何用这两种方法来展开参数包。 递归函数方式展开参数包通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,来看看下面的例子。 上例会输出每一个参数,直到为空时输出empty。展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。递归调用的过程是这样的: 上面的递归终止函数还可以写成这样: 更多C/C++学习资料,请私信我“代码”,即可获取 修改递归终止函数后,上例中的调用过程是这样的: 更多C/C++学习资料,请私信我“代码”,即可获取 当参数包展开到最后一个参数时递归为止。再看一个通过可变模版参数求和的例子: 更多C/C++学习资料,请私信我“代码”,即可获取 sum 在展开参数包的过程中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是一样的。 逗号表达式展开参数包更多C/C++学习资料,请私信我“代码”,即可获取 递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归,这样可能会感觉稍有不便。有没有一种更简单的方式呢?其实还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。比如前面print的例子可以改成这样: 更多C/C++学习资料,请私信我“代码”,即可获取 这个例子将分别打印出1,2,3,4四个数字。这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式,比如:d = (a = b, c); 这个表达式会按顺序执行:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c。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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下: 更多C/C++学习资料,请私信我“代码”,即可获取 上面的例子将打印出每个参数,这里如果再使用C++14的新特性泛型lambda表达式的话,可以写更泛化的lambda表达式了:expand([](auto i){cout<> 结束语更多C/C++学习资料,请私信我“代码”,即可获取 今天的内容就到这里了哦,后期更新C++11中的可变参数模板类呦, 使用可变模板参数的关键是如何展开参数包,展开参数包的过程是很精妙的,体现了泛化之美、递归之美,正是因为它具有神奇的“魔力”,所以我们可以更泛化的去处理问题。 |
|
来自: 山峰云绕 > 《c加加c井号面向对象》