可变长参数 VS C++11 可变长模板

2023-11-11

转: https://blog.csdn.net/zj510/article/details/36633603

C 可变长参数 VS C++11 可变长模板
2014年07月03日 13:50:32
阅读数:10437

有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数。比如C函数printf().

我们可以这么调用。

[cpp]  view plain  copy
  1. printf("name: %s, number: %d""Obama", 1);  
那么这个函数是怎么实现的呢?其实C语言支持可变长参数的。

我们举个例子,

[cpp]  view plain  copy
  1. double Sum(int count, ...)  
  2. {  
  3.     va_list ap;  
  4.     double sum = 0;  
  5.   
  6.     va_start(ap, count);  
  7.   
  8.     for (int i = 0; i < count; ++i)  
  9.     {  
  10.         double arg = va_arg(ap, double);  
  11.         sum += arg;  
  12.     }  
  13.   
  14.     va_end(ap);  
  15.   
  16.     return sum;  
  17. }  
上面这个函数,接受变长参数,用来把所有输入参数累加起来。可以这么调:

[cpp]  view plain  copy
  1. double sum = Sum(4, 1.0, 2.0, 3.0, 4.0);  
计算结果是10,很好。

那么C语言的这个函数有什么问题呢?

1. 函数本身并不知道传进来几个参数,比如我现在多传一个参数,或者少传一个参数,那么函数本身是检测不到这个问题的。这就可能会导致未定义的错误。

2. 函数本身也不知道传进来的参数类型。以上面的例子,假如我把第二个参数1.0改成一个字符串,又如何?答案就是会得到未定义的错误,也就是不知道会发生什么。

3. 对于可变长参数,我们只能用__cdecl调用约定,因为只有调用者才知道传进来几个参数,那么也只有调用者才能维持栈平衡。如果是__stdcall,那么函数需要负责栈平衡,可是函数本身根本不知道有几个参数,函数调用结束后,根本不知道需要将几个参数pop out。(注:某些编译器如VS,如果用户写了个__stdcall的可变长参数函数,VS会自动转换成__cdecl的,当然这是编译器干的事情)

在C++语言里面,在C++11之前,C++也只是兼容了C的这种写法,而C++本身并没有更好的替代方案。其实对于C++这种强类型语言而言,C的这种可变长方案等于是开了个后门,函数居然不知道传进来的参数是什么类型。所以在C++11里面专门提供了对可变长参数的更现代化的支持,那就是可变长模板。

模板参数包(template parameter pack)

[cpp]  view plain  copy
  1. template<typename... A> class Car;  
typename...就表示一个模板参数包。可以这么来实例化模板:

[cpp]  view plain  copy
  1. Car<intchar> car;  
再来看一个更加具体的例子:

[cpp]  view plain  copy
  1. template<typename T1, typename T2> class Car{};  
  2. template<typename... A> class BMW : public Car<A...>{};  
  3. BMW<intchar> car;  
在这个例子里面,BMW是一个可变参数的模板,它继承于类Car. 那么BMW<int, char> car;在进行模板推导的时候,可以认为变成Car<int, char>了。这其中的功劳应该属于A...,

A...称之为包扩展(pack extension),包扩展是可以传递的。比如继承的时候,或者直接在函数参数里面传递。然后当编译器进行推导的时候,就会对这个包扩展进行展开,上面的例子,A...就展开成了int, char。C++11定义了可以展开包的几个地方:

1. 表达式

2. 初始化列表

3. 基类描述列表

4. 类成员初始化列表

5. 模板参数列表

6. 通用属性列表

7. lamda函数的捕捉列表

其他地方是不能展开的。

针对上面的例子,如果我们改成BMW<int, char, int> car, 会如何呢?编译的时候就直接报错了,

Error 1  error C2977: 'Car' : too many template arguments  d:\study\consoleapplication2\variablelengthparameters\variablelengthparameters.cpp271 VariableLengthParameters
这是因为当展开的时候,A...变成了int, char, int了,可能基类根本就没有3个模板参数,所以推导就出错了。那如果这样的话,可变长参数还是啥意义呢?这等于每次的参数个数还是固定的啊。当然不会这么傻,其实C++11可以通过递归来实现真正的可变长的。看下面的代码。

[cpp]  view plain  copy
  1. template<typename... A> class BMW{};  
  2.   
  3. template<typename Head, typename... Tail>  
  4. class BMW<Head, Tail...> : public BMW<Tail...>  
  5. {  
  6. public:  
  7.     BMW()  
  8.     {  
  9.         printf("type: %s\n"typeid(Head).name());  
  10.     }  
  11. private:  
  12.     Head head;  
  13. };  
  14.   
  15. template<> class BMW<>{};  // 边界条件  
  16.   
  17. BMW<intcharfloat> car;  

如果我们运行这段代码,会发现构造函数被调用了3次。第一次得到的类型是float,第二次是char,第三次是int。这就好像模板实例化的时候层层展开了。实际上也就是这么一回事情。这里使用了C++模板的特化来实现了递归,每递归一次就得到一个类型。看一下对象car里面有什么:


可以清晰的看到car里面有三个head。基类里面的head是float,第二个head是char,第三个head是int。

有了这个基础之后,我们就可以实现我们的可变长模板类了,std::tuple就是个很好的例子。可以看看它的源代码,这里就不再介绍了。

可变长模板不光可以用于类的定义,也可以用户函数模板。接下来,就用可变长参数来实现一个Sum函数,然后跟上面的C语言版本做对比。

可变长模板实现Sum函数

直接看代码:

[cpp]  view plain  copy
  1. template<typename T1, typename... T2> double Sum2(T1 p, T2... arg)  
  2. {  
  3.     double ret = p + Sum2(arg...);  
  4.   
  5.     return ret;  
  6. }  
  7.   
  8. double Sum2()  // 边界条件  
  9. {  
  10.     return 0;  
  11. }  
在上面的代码里面,可以很清楚的看到递归。

[cpp]  view plain  copy
  1. double ret2 = Sum2(1.0, 2.0, 3.0, 4.0);  
这条调用代码同样得到结果10.这样过程可以理解为,边界条件的函数先执行完毕,然后4.0的执行完毕,再3.0,2.0,1.0以此被执行完毕。一个典型的递归。

ok,那么跟C语言版本相比,又有哪些好处呢?

变长模板优点

之前提到的几个C语言版本的主要缺点:

1. 参数个数,那么对于模板来说,在模板推导的时候,就已经知道参数的个数了,也就是说在编译的时候就确定了,这样编译器就存在可能去优化代码。

2. 参数类型,推导的时候也已经确定了,模板函数就可以知道参数类型了。

3. 既然编译的时候就知道参数个数和参数类型了,那么调用约定也就没有限制了。

来实验一下第二点吧

[cpp]  view plain  copy
  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3.     double ret1 = Sum(4, 1.0, 2.0, 3.0, 4.0, "abcd");  
  4.     double ret2 = Sum2(1.0, 2.0, 3.0, 4.0, "abcd");  
  5.       
  6.       
  7.     return 0;  
  8. }  
Sum是C语言版本,最后一个参数传了个字符串,但是Sum函数是无法检测这个错误的。结果也就是未定义。

Sum2是个模板函数,最后一个参数也是字符串,在编译的时候就报错了,

Error 1  error C2111: '+' : pointer addition requires integral operandd:\study\consoleapplication2\variablelengthparameters\variablelengthparameters.cpp291 VariableLengthParameters

double无法和字符串相加,这样在编译的时候就告诉我们这个错误了,我们就可以修复它,但是C语言的版本不会报错,代码也就失控了,不知道会得到什么结果。

怎么样,变长模板比C语言的变长参数好一些吧。

所以,我们还是尽可能使用C++11的变长模板吧。

最后一个问题,为什么使用变长参数呢?有些人可能会问,是不是可以把所有的参数放到一个list里面,然后函数遍历整个list,再相加呢?good point, 

如果所有的参数类型都一样,确实可以这么做,但是如果参数类型不一样呢?那怎么放到一个list里面?像C++这种强类型语言可能做不到吧,确实弱类型语言比如php,python等,确实可以这么做。根据我的理解,脚本语言等弱类型语言不需要变长参数吧,或者不重要。但是C++还是需要的,

用可变长模板就没这个问题了,就算参数类型不一样,只要对应的类型有对应的操作,就没问题。当然像上面的例子,如果没有重载+,那么编译的时候就报错,这不就是我们需要的吗?

附:

[cpp]  view plain  copy
  1. // VariableLengthParameters.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5.   
  6. #include "stdarg.h"  
  7. #include <typeinfo>  
  8.   
  9. double Sum(int count, ...)  
  10. {  
  11.     va_list ap;  
  12.     double sum = 0;  
  13.   
  14.     va_start(ap, count);  
  15.   
  16.     for (int i = 0; i < count; ++i)  
  17.     {  
  18.         double arg = va_arg(ap, double);  
  19.         sum += arg;  
  20.     }  
  21.   
  22.     va_end(ap);  
  23.   
  24.     return sum;  
  25. }  
  26.   
  27. template<typename T1, typename... T2> double Sum2(T1 p, T2... arg)  
  28. {  
  29.     double ret = p + Sum2(arg...);  
  30.   
  31.     return ret;  
  32. }  
  33.   
  34. double Sum2()  
  35. {  
  36.     return 0;  
  37. }  
  38.   
  39.   
  40. template<typename... A> class BMW{};  
  41.   
  42. template<typename Head, typename... Tail>  
  43. class BMW<Head, Tail...> : public BMW<Tail...>  
  44. {  
  45. public:  
  46.     BMW()  
  47.     {  
  48.   
  49.         printf("type: %s\n"typeid(Head).name());  
  50.     }  
  51.   
  52.     Head head;  
  53. };  
  54.   
  55. template<> class BMW<>{};  
  56.   
  57. BMW<intcharfloat> car;  
  58.   
  59. int _tmain(int argc, _TCHAR* argv[])  
  60. {  
  61.     double ret1 = Sum(4, 1.0, 2.0, 3.0, 4.0);  
  62.     double ret2 = Sum2(1.0, 2.0, 3.0, 4.0);  
  63.       
  64.       
  65.     return 0;  
  66. }  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

可变长参数 VS C++11 可变长模板 的相关文章

随机推荐

  • Android下拉刷新效果实现

    本文主要包括以下内容 自定义实现pulltorefreshView 使用google官方SwipeRefreshLayout 下拉刷新大致原理 判断当前是否在最上面而且是向下滑的 如果是的话 则加载数据 并更新界面 自定义实现pulltor
  • Matlab中dir使用中遇到的一些问题

    今天调程序时遇到一个bug 感觉有点意思 也许有人会遇到类似的问题吧 问题 说手上有一段代码 原本是希望在一个文件夹中读取出其中所有音频文件的 tdir dir fullfile SoundDir SoundFileName NumSoun
  • 出现command 'gcc' failed with exit status 1 解决方案

    在centos7 上用pip 安装psutil的时候很不幸的出现了如下错误 pip install psutil Collecting psutil Using cached psutil 5 3 1 tar gz Installing c
  • python 角度判断_大牛带你打牢Python基础,看看这10语法

    都说Python简单 易懂 但是有时候却又很深奥 许多人都觉的自己学会了 却老是写不出项目来 对很多常用包的使用也并不熟悉 学海无涯 我们先来了解一些Python中最基本的内容 1 数值 数值包括整型和浮点型 分别对应整数和浮点数 后者精度
  • Python3 列表笔记

    列表 使用 括起来的一个个元素的集合 1 列表的元素使用 进行分割 2 列表的元素可以是任意数据类型 1 创建列表 list huarzil 32 3 14 True zhuangsan lisi 32 29 30 name height
  • linux学习笔记--网络编程

    目录 概念 协议 网络应用设计模式 分层模型 协议格式 TCP状态 网络名词 socket编程 套接字 字节序 函数 socket bind listen accept connect C S模型 server client 封装 高并发服
  • iview 数据表格 固定列拉倒底部后刷新出现错行问题

    很多小伙伴肯定遇到过这个组件问题 下面只需要一行即可搞定 vue方法 首先我们在mixin js里封装一个方法 pageSizeChange pageSize 每页显示数量变更 this searchParams limit pageSiz
  • C#中,浮点数的比较和decimal

    浮点数 C 的浮点数类型 float double 当我们定义一个浮点数可以 可以使用var 关键字 可以做类型推断 定义float类型 数字末尾需要加上 F或者是f 定义一个double类型 double a1 1 1 var a2 1
  • <转>企业应用架构 --- 分层

    系统架构师 基础到企业应用架构 分层 上篇 一 前言 大家好 接近一年的时间没有怎么书写博客了 一方面是工作上比较忙 同时生活上也步入正轨 事情比较繁多 目前总算是趋于稳定 可以有时间来完善以前没有写完的系列 也算是对自己这段时间工作和生活
  • 程序流程图是什么?基本流程图讲解

    程序流程图是什么 程序流程图是流程图的其中一种分类 又称程序框图 指用特定图形符号加上对应的文字描述表示程序中所需要的各项操作或判断的图示 程序流程图除了说明程序的流程顺序外 着重于说明程序的逻辑性 一 程序流程图特点 当程序流程中有较多循
  • 动态规划经典例题-国王的金矿问题

    金矿问题 问题概述 有一位国王拥有5座金矿 每座金矿的黄金储量不同 需要参与挖掘的工人人数也不同 例如有的金矿储量是500kg黄金 需 要5个工人来挖掘 有的金矿储量是200kg黄金 需要3个工人来挖 掘 如果参与挖矿的工人的总数是10 每
  • 转:FindBugs,第 2 部分: 编写自定义检测器

    FindBugs 第 2 部分 编写自定义检测器 如何编写自定义检测器以查找特定于应用程序的问题 FindBugs 是一种可以扩展和定制以满足自己团队独特要求的静态分析工具 在本系列的第 2 部分中 高级软件工程师 Chris Grinds
  • odoo12 用户(users) 权限管理界面分析

    起因 由于需要了解 odoo的权限管理 去看了下 odoo 是如何给用户赋权限的 发现好多不能理解 因此 打算从 user 的xml开始 看里面到底是什么意思 第一步 肯定查看user的xml 找user源码 odoo odoo addon
  • delphi xe 10.3 访问 linux 7 mysql 5.7.20

    下载 https cdn mysql com archives mysql 5 7 mysql 5 7 34 win32 zip 解压 并复制lib目录下的所有文件到 X Program Files x86 Embarcadero Stud
  • MySQL崩溃修复案例

    问题描述 研究MySQL源代码 调试并压测MySQL源代码时 MySQL崩溃了 问题是它竟然崩溃了 而且还损坏了InnoDB文件 还好是在调试环境下发生的 赶紧看看如何解决这个问题 经过一系列的查阅资料 验证 对比 MySQL源码调试跟踪
  • 单线程的Redis为什么这么快

    一 为什么Redis是单线程的 Redis 是基于内存的操作 而CPU 不是 Redis 的瓶颈 Redis 的瓶颈最有可能是机器内存的 大小或者网络带宽 同时 单线程的实现更加简单和经济 采用单线程可以使指令串行 不用额外 维护锁机制 避
  • java通过反射创建对象的两种方式

    我个人觉得我自己是个比较粗心的人 所以各位大佬发现有什么不对的地方还请留言告知 在java中 通过反射创建对象有两种方式 使用Class对象的newInstance 方法来创建对象 具体步骤是 1 获取类的Class对象 有三种方式可以获取
  • 深度学习系列:阿里DIN模型的原理和代码实现

    一 前言 今天介绍阿里巴巴的DIN网络 不得不说 阿里妈妈的大佬是真的多 经常都会更新非常多的创造性的东西 比如DIN中使用的自适应正则化技术以及Dice激活函数以及注意力机制的使用 并且值得注意的是DIN网络中使用的注意力机制还挺多的 哈
  • C语言中不定参数函数

    在我们平常调用函数的时候 会进行传参 调用的函数也会有参数去接收 数量和类型都是对应的 而不定参数函数是指对一个函数传参 参数的个数可以不确定 接下来 我就简单的叙述一下不定参数函数的原理及应用 在我们刚学C语言的时候 大多会首先接触pri
  • 可变长参数 VS C++11 可变长模板

    转 https blog csdn net zj510 article details 36633603 C 可变长参数 VS C 11 可变长模板 2014年07月03日 13 50 32 阅读数 10437 有些时候 我们定义一个函数