【C++】—— C++11之可变参数模板

前言:

  • 在C语言中,我们谈论了有关可变参数的相关知识。在C++11中引入了一个新特性---即可变参数模板。本期,我们将要介绍的就是有关可变参数模板的相关知识!!!

目录

序言

(一)可变参数模板函数

1、sizeof... 运算符

(二)扩展参数包的两种方法

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

2、逗号表达式展开参数包

3、两种方法的优缺点

总结


序言

C++11的新特性可变参数模板能够让我们可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大家如果有需要,再可以深入学习。
 


(一)可变参数模板函数

在了解模板函数和模板类之前,我们需要先知道几个概念:

一个 可变参数模板 就是一个接收可变数目参数的模板函数或者是模板类。可变数目的参数被称为 函数包。存在两种函数包:

  • 模板参数包:使用 typename/class ... Args 来指出 Args 是一个模板参数包,表示零个或多个模板参数
  • 函数参数包:使用 Args ... rest 来指出 rest 是一个函数参数包,表示零个或多个函数参数

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

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

【解释说明】

  • 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

与往常一样,编译器从函数的实参推倒模板参数类型。对于一个可变参数模型,编译器还会推断包中参数的数目。例如,看下面一个简单例子:

// 可变参数函数模板
template<typename... Args>
void printArgs(Args... args) 
{cout << "Number of arguments: " << sizeof...(args) << endl;cout << endl;
}int main() {printArgs(1, 2, 3);                  // 输出:Number of arguments: 3   Arguments: 1 2 3printArgs("Hello", 3.14, 'c');       // 输出:Number of arguments: 3   Arguments: Hello 3.14 cprintArgs(10);                       // 输出:Number of arguments: 1   Arguments: 10printArgs();                         // 输出:Number of arguments: 0   Arguments:return 0;
}

 编译器会为 printArgs 实例化出以下四个不同版本,我们看下上面程序的汇编代码:


1、sizeof... 运算符

大家可以发现上述代码样例中,我使用了 sizeof...  这样的字段。那么这个是什么意思呢?

其实是 sizeof...   C++11引入的一元运算符,用于获取可变模板参数包中的参数数量:

  • 因此,接下来我尝试运行一下代码,看结果:

 【解释说明】

  • 在上面的示例中,我们定义了一个函数模板  printArgs ,它接受可变数量的模板参数。在函数模板中,我们使用  sizeof...   来获取参数包 args  中的参数数量

【小结】

  1.  运算符可以用于类模板和函数模板中,用于获取参数包的大小;
  2. 它在处理可变参数模板时非常有用,可以帮助我们实现更加灵活和通用的代码。

(二)扩展参数包的两种方法

因为我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

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

通过递归函数方式展开参数包是一种常见的处理可变参数模板的方法。这种方法利用函数的递归调用来依次处理参数包中的每个参数。

  • 下面是一个示例展示了如何使用递归函数方式展开参数包:
// 递归终止函数
template <class T>
void ShowList(const T& t)
{cout << t << endl;
}// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

【解释说明】

 在上面的示例中,ShowList 是一个展开函数模板,用于递归展开参数包并输出每个参数的值。

  •  ShowList(const T& t):是递归终止函数,用于处理只有一个参数的情况。它接受一个参数 t,并将其输出到标准输出流;
  • ShowList(T value, Args... args):是展开函数模板的递归部分。它接受一个参数 value 和更多的参数包 Args... args。在函数体内,它首先输出 value 的值,然后通过递归调用 ShowList 函数来处理剩余的参数包 args...

以下是运行上述代码的输出结果:

 通过递归函数方式展开了参数包,并成功输出了每个参数的值。这是一种常见的使用递归的方法来处理可变参数模板的方式。

 💨【注意】 当定义可变参数版本的 ShowList 时,非可变参数版本必须要声明在可变参数版本 (递归体) 的作用域当中,否则会导致无限递归!!!


2、逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

 

  • 下面是一个示例展示了如何利用逗号表达式来展开参数包:
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;
}

【解释说明】

在上面的示例中,PrintArg 是一个简单的辅助函数模板,用于打印参数的值。

  1. ShowList  是一个展开函数模板,它接受可变数量的参数,并使用逗号表达式来展开参数包。在展开过程中,每个参数都会被传递给 PrintArg 函数进行处理,并且逗号表达式的结果被忽略。
  2. 在 main 函数中,我们调用了 ShowList 函数,并传递了不同数量和类型的参数。通过逗号表达式展开参数包,每个参数都会被依次处理,并调用 PrintArg 函数将其值输出到标准输出流。

以下是运行上述代码的输出结果:

上述代码我们还可以像下述这样去进行实现操作:

 【说明】

  1.  expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0;
  2. 同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)];
  3. 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分 printarg(args) 打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
     

3、两种方法的优缺点

逗号表达式扩展方式和递归包扩展方式都可以用于展开可变参数模板,但它们具有不同的优缺点

具体如下:

逗号表达式扩展方式的优点:

  1. 简洁性:使用逗号表达式可以在一行代码中展开参数包,代码量较少且结构清晰。
  2. 高效性:逗号表达式会在编译时展开参数包,可以生成高效的代码。
  3. 可读性:逗号表达式展开参数包的语法较为直观,易于阅读和理解。

逗号表达式扩展方式的缺点:

  1. 顺序限制:逗号表达式展开参数包的顺序是从左到右,无法灵活地改变参数的处理顺序。
  2. 局限性:逗号表达式虽然简洁,但在某些复杂的情况下可能比较难以处理。

递归包扩展方式的优点:

  1. 灵活性:利用递归函数方式展开参数包可以在每一步递归中对参数进行处理和逻辑操作,具有更高的灵活性。
  2. 可控性:递归包展开方式可以通过递归函数中的控制语句和条件语句对参数包的展开进行控制。

递归包扩展方式的缺点:

  1. 代码冗余:递归函数方式展开参数包可能需要更多的代码量来实现,相对于逗号表达式方式来说,代码可能会更冗长。
  2. 可读性:递归函数方式展开参数包的代码结构可能比较复杂,不够直观。

根据具体的情况和需求,可以根据代码的复杂度和可读性的要求选择使用逗号表达式扩展方式或递归包扩展方式。逗号表达式方式适用于简单的参数展开,而递归包方式则适用于复杂的参数展开,可以更灵活地进行处理。


总结

以上便是关于c++11 可变参数模板函数的全部知识讲解!!感谢大家的观看与支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/106909.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

0基础学习VR全景平台篇 第90篇:智慧眼-数据统计

【数据统计】是按不同条件去统计整个智慧眼项目中的热点&#xff0c;共包含四大块&#xff0c;分别是数据统计、分类热点、待审核、回收站&#xff0c;下面我们来逐一进行介绍。 1、数据统计 ① 可以按所属分类、场景分组、所属场景、热点类型以及输入热点名去筛选对应的热点&…

文生图模型之Stable Diffusion

原始文章地址 autoencoder CLIP text encoder tokenizer最大长度为77&#xff08;CLIP训练时所采用的设置&#xff09;&#xff0c;当输入text的tokens数量超过77后&#xff0c;将进行截断&#xff0c;如果不足则进行paddings&#xff0c;这样将保证无论输入任何长度的文本&…

Kaniko在containerd中无特权快速构建并推送容器镜像

目录 一、kaniko是什么 二、kaniko工作原理 三、kanijo工作在Containerd上 基于serverless的考虑&#xff0c;我们选择了kaniko作为镜像打包工具&#xff0c;它是google提供了一种不需要特权就可以构建的docker镜像构建工具。 一、kaniko是什么 kaniko 是一种在容器或 Kube…

【Linux】进程状态|僵尸进程|孤儿进程

前言 本文继续深入讲解进程内容——进程状态。 一个进程包含有多种状态&#xff0c;有运行状态&#xff0c;阻塞状态&#xff0c;挂起状态&#xff0c;僵尸状态&#xff0c;死亡状态等等&#xff0c;其中&#xff0c;阻塞状态还包含深度睡眠和浅度睡眠状态。 个人主页&#xff…

Diffusion Models for Image Restoration and Enhancement – A Comprehensive Survey

图像恢复与增强的扩散模型综述 论文链接&#xff1a;https://arxiv.org/abs/2308.09388 项目地址&#xff1a;https://github.com/lixinustc/Awesome-diffusion-model-for-image-processing/ Abstract 图像恢复(IR)一直是低水平视觉领域不可或缺的一项具有挑战性的任务&…

算法竞赛入门【码蹄集新手村600题】(MT1220-1240)C语言

算法竞赛入门【码蹄集新手村600题】(MT1220-1240&#xff09;C语言 目录MT1221 分数的总和MT1222 等差数列MT1223 N是什么MT1224 棋盘MT1225 复杂分数MT1226 解不等式MT1227 宝宝爬楼梯MT1228 宝宝抢糖果MT1229 搬家公司MT1230 圆周率MT1231圆周率IIMT1232 数字和MT1233 数字之…

适配器模式实现stack和queue

适配器模式实现stack和queue 什么是适配器模式&#xff1f;STL标准库中stack和queue的底层结构stack的模拟实现queue的模拟实现 什么是适配器模式&#xff1f; 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff…

时间和日期--Python

1. 时间&#xff1a;time模块 总结&#xff1a;2. datetime模块 相比与time模块&#xff0c;datetime模块的接口更直观、更容易调用 2.1 datetime模块定义的类 &#xff08;1&#xff09;datetime.date:表示日期的类。常用的属性有&#xff1a;year、month、day; &#xff…

k8s节点pod驱逐、污点标记

一、设置污点&#xff0c;禁止pod被调度到节点上 kubectl cordon k8s-node-145 设置完成后&#xff0c;可以看到该节点附带了 SchedulingDisabled 的标记 二、驱逐节点上运行的pod到其他节点 kubectl drain --ignore-daemonsets --delete-emptydir-data k8s-node-145 显示被驱逐…

抓包相关,抓包学习

检查网络流量 - 提琴手经典 (telerik.com) Headers Reference - Fiddler Classic (telerik.com) 以上是fiddler官方文档 F12要勾选保留日志 不勾选的话跳转到新页面之前页面的日志不会在下方显示 会保留所有抓到的包 如果重定向到别的页面 F12抓包可能看不到响应信息,但是…

【PHP】PHP开发教程-PHP开发环境安装

1、PHP简单介绍 PHP&#xff08;全称&#xff1a;Hypertext Preprocessor&#xff09;是一种广泛使用的开放源代码脚本语言&#xff0c;特别适用于Web开发。它嵌入在HTML中&#xff0c;通过在HTML文档中添加PHP标记和脚本&#xff0c;可以生成动态的、个性化的Web页面。 PHP最…

Vant 4.6.4发布,增加了一些新功能,并修复了一些bug

导读Vant 4.6.4发布,增加了一些新功能&#xff0c;并修复了一些bug等。 新功能 feat(area-data): 更新芜湖的县区数据&#xff0c;由 nivin-studio 在 #12122 中贡献feat(Locale): 添加塞尔维亚语到国际化&#xff0c;由 RogerZXY 在 #12145 中贡献feat(ImagePreview): 添加 c…

百望云华为云共建零售数字化新生态 聚焦数智新消费升级

零售业是一个充满活力和创新的行业&#xff0c;但也是当前面临很大新挑战和新机遇的行业。数智新消费时代&#xff0c;数字化转型已经成为零售企业必须面对的重要课题。 8 月 20 日-21日&#xff0c;以“云上创新 韧性增长”为主题的华为云数智新消费创新峰会2023在成都隆重召…

Redis从基础到进阶篇(二)----内存模型与内存优化

目录 一、缓存通识 1.1 ⽆处不在的缓存 1.2 多级缓存 &#xff08;重点&#xff09; 二、Redis简介 2.1 什么是Redis 2.2 Redis的应用场景 三、Redis数据存储的细节 3.1 Redis数据类型 3.2 内存结构 3.3 内存分配器 3.4 redisObject 3.4.1 type 3.4.2 encoding 3…

微积分基本概念

微分 函数的微分是指对函数的局部变化的一种线性描述。微分可以近似地描述当函数自变量的取值作足够小的改变时&#xff0c;函数的值是怎样改变的。。对于函数 y f ( x ) y f(x) yf(x) 的微分记作&#xff1a; d y f ′ ( x ) d x d_y f^{}(x)d_x dy​f′(x)dx​ 微分和…

什么是响应式设计(Responsive Design)?如何实现一个响应式网页?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式设计&#xff08;Responsive Design&#xff09;⭐ 如何实现一个响应式网页&#xff1f;1. 弹性网格布局2. 媒体查询3. 弹性图像和媒体4. 流式布局5. 优化导航6. 测试和调整7. 图片优化8. 字体优化9. 渐进增强10. 面向移动优先11. …

芯讯通SIMCOM A7680C (4G Cat.1)AT指令测试 TCP通信过程

A7680C TCP通信 1、文档准备 去SIMCOM官网找到A7680C的AT指令集 AT指令官网 进入官网有这么多AT指令文件&#xff0c;只需要找到你需要用到的&#xff0c;这里我们用到了HTTP和TCP的&#xff0c;所以下载这两个即可。 2、串口助手 任意准备一个串口助手即可 这里我使用的是XC…

C++笔记之设计模式:setter函数、依赖注入

C笔记之设计模式&#xff1a;setter函数、依赖注入 参考笔记&#xff1a; 1.C笔记之静态成员函数可以在类外部访问私有构造函数吗&#xff1f; 2.C笔记之设计模式&#xff1a;setter函数、依赖注入 3.C笔记之两个类的实例之间传递参数——通过构造函数传递类对象的方法详细探究…

【C++】进一步认识模板

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、非类型模板参数二、模板的特…