版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)
前言
本文要讨论的两种变长参数函数的形式分别为:
- c语言的方式
- c++11的变长模板参数
c语言的方式
在C++中, 通过包含<cstdarg>
就能够使用c语言中<stdarg.h>
里面定义的几个宏来完成变长参数的处理,先看一个实例:
例1:使用变长参数函数求和
运行结果: 测试通过
使用va_list这种处理方式很早以前就有了,而且资料也比较多,在此不再多说,下面给出一个表格总结下几个关键宏(va_list是一个类型,不是宏)的作用:
名称 |
参数 |
作用 |
va_list |
无 |
定义变长参数指针类型 |
va_start(ap, paramN) |
ap: va_list类型的变量, 用来获取变长参数列表,基于paramN进行计算。</br> paramN: 函数参数列表里最后一个有名字的参数,比如vsum里的count。 |
初始化va_list类型的变量ap,使其获得到变长参数列表 |
va_end(ap) |
ap: va_list类型的变量, 之前应该用va_start初始化过 |
结束使用变长参数列表,应该在调用了va_start的函数返回之前调用va_end |
va_arg(ap, type) |
ap: va_list类型的变量, 保存了变长参数列表的当前状态 </br> type: ap中当前位置的变量类型 |
返回ap当前位置的参数类型为type,同时修改ap的状态,使它的当前状态指向下一个变长参数列表里的参数。 |
va_copy(dst, src) |
dst : 未经初始化的va_list类型的变量; </br> src要被复制的va_list变量 |
复制src,保持其当前状态到dst,之后对dst的变长参数处理操作将会与操作src一样。操作结束后,要使用va_end(dst)来做清理工作 |
c++11中引入了变长模板参数这一功能,包括类模板和函数模板都可以使用变长模板参数
下面就演示一下如何使用变长模板参数来实现例子1中的变长参数求和。
使用c++11的变长模板函数
例2:
测试结果:通过
代码分析:
与va_list的实现方式比较:
- 模板函数更加灵活,不需要指定参数个数count,且参数类型可以不同. va_list方案中必须都是同一种类型int才能统一处理.
- 不必显示指定参数类型(va_list方案中需要va_arg(ap, type)来指定type), 函数模板的版本中不需要对类型进行判断,只要支持“+”操作的类型即可被当做参数传入vsum.
- 灵活性大也意味着容易犯错, 对于函数模板的vsum:</br>
请分析vsum(1, 2.5, 3)的返回值是多少? 它和vsum(1.5, 2, 3)的结果一样吗,都是6.5?</br>
vsum(1, 2.5, 3) 将返回6, 而vsum(1.5, 2, 3)将返回6.5. </br>
这是因为vsum的返回值被定义为第一个参数的返回类型(int),也就是说vsum(1, 2.5, 3)的6.5在返回时被转为6.
可以使用如下测试代码,来确定vsum(1, 2.5, 3) 和 vsum(1.5, 2, 3)的返回类型.
使用c++11的变长参数类模板来实现可变参数求和 vsum
例3:使用模板类
测试结果: 通过
代码分析:
思路是通过模板元编程的方式,在编译期完成计算。类似经典的模板元编程求阶乘的方法。 模板参数类型使用了非类型模板(nontype template)参数:long。 变长模板的展开通常都是通过递归的方式。
总结,对比两种方式可以看出,
- c语言中,va_list的方式写法比较直接、易懂,但是不是c++解决问题的方式,不够通用,限制太多, 且用到了宏(Effective c++里不推荐宏)。
- 变长模板参数和变长模板类能实现va_list的功能,且灵活性比较大,使用函数和类,解决方法更为通用。其中类模板的方式, 和的计算在编译期就已完成。
源代码
本文的测试代码使用了google的c++单元测试框架gtest,源码请到我的github仓库查看:
git clone git@github.com:elloop/CS.cpp.git
下来,使用vs2013打开CS.cpp.sln,设置TrainingGround为启动项目, 构建运行即可。
参考链接