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

news2024/10/5 14:42:23

前言:

  • 在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 3
    printArgs("Hello", 3.14, 'c');       // 输出:Number of arguments: 3   Arguments: Hello 3.14 c
    printArgs(10);                       // 输出:Number of arguments: 1   Arguments: 10
    printArgs();                         // 输出: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.coloradmin.cn/o/932924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习10:Attention 机制

目录 Attention 的本质是什么 Attention 的3大优点 Attention 的原理 Attention 的 N 种类型 Attention 的本质是什么 Attention&#xff08;注意力&#xff09;机制如果浅层的理解&#xff0c;跟他的名字非常匹配。他的核心逻辑就是「从关注全部到关注重点」。 Attention…

ServiceManager接收APP的跨进程Binder通信流程分析

现在一起来分析Server端接收&#xff08;来自APP端&#xff09;Binder数据的整个过程&#xff0c;还是以ServiceManager这个Server为例进行分析,这是一个至下而上的分析过程。 在分析之前先思考ServiceManager是什么&#xff1f;它其实是一个独立的进程&#xff0c;由init解析i…

windows11不允许安装winpcap4.1.3

问题&#xff1a;下载安装包后在安装时显示与电脑系统不兼容&#xff0c;不能安装。 原因&#xff1a;winpcap是一个用于Windows操作系统的网络抓包库&#xff0c;有一些安全漏洞&#xff0c;存在被黑客攻击的风险。Windows11为了加强系统安全而禁用了这个库&#xff0c;因此不…

java.8 - java -overrideoverload 重写和重载

重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变&#xff0c;核心重写&#xff01; 重写的好处在于子类可以根据需要&#xff0c;定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。 重写方法不…

【GAMES202】Real-Time Environment Mapping1—实时环境光照1

一、Distance field soft shadows Inigo Quilez :: computer graphics, mathematics, shaders, fractals, demoscene and more (iquilezles.org) 在开始我们的实时环境光照之前&#xff0c;我们再说一种现在的实现实时软阴影的方式&#xff0c;也就是Distance field soft shado…

SpringBoot实现文件上传和下载笔记分享(提供Gitee源码)

前言&#xff1a;这边汇总了一下目前SpringBoot项目当中常见文件上传和下载的功能&#xff0c;一共三种常见的下载方式和一种上传方式&#xff0c;特此做一个笔记分享。 目录 一、pom依赖 二、yml配置文件 三、文件下载 3.1、使用Spring框架提供的下载方式 3.2、通过IOUti…

分布式 - 服务器Nginx:一小时入门系列之 return 指令

文章目录 1. return 指令语法2. return code URL 示例3. return code text 示例4. return URL 示例 1. return 指令语法 return指令用于立即停止当前请求的处理&#xff0c;并返回指定的HTTP状态码和响应头信息&#xff0c;它可以用于在Nginx中生成自定义错误页面&#xff0c;…

分布式事务-seata框架

文章目录 分布式事务0.学习目标1.分布式事务问题1.1.本地事务1.2.分布式事务1.3.演示分布式事务问题 2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2.1.3.分区容错2.1.4.矛盾 2.2.BASE理论2.3.解决分布式事务的思路 3.初识Seata3.1.Seata的架构3.2.部署TC服务3.3.微服务集成S…

CAPL - Panel和TestModule结合实现测试项可选

目录 一、定义脚本编号和脚本组编号 1、测试组定义 2、测试脚本编号定义

【C++】初步认识模板

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

Java10(异常处理)

0.复习面向对象 1.异常的体系结构 异常&#xff1a;在Java语言中&#xff0c;将程序执行中发生的不正常情况.(开发中的语法错误和逻辑错误不是异常) 异常事件分两类&#xff08;它们上一级为java.lang.Throwable&#xff09;&#xff1a; Error Java虚拟机无法解决的严重问…

算法通过村第三关-数组黄金笔记|数组难解

文章目录 前言数组中出现超过一半的数字数组中只出现一次的数字颜色的分类问题(荷兰国旗问题)基于冒泡排序的双指针&#xff08;快慢指针&#xff09;基于快排的双指针&#xff08;对撞指针&#xff09; 总结 前言 提示&#xff1a;苦不来自外在环境中的人、事、物&#xff0c;…

最新人工智能源码搭建部署教程/ChatGPT程序源码/AI系统/H5端+微信公众号版本源码

一、AI系统 如何搭建部署人工智能源码、AI创作系统、ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01; SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、…

Vue3.0极速入门- 目录和文件说明

目录结构 以下文件均为npm create helloworld自动生成的文件目录结构 目录截图 目录说明 目录/文件说明node_modulesnpm 加载的项目依赖模块src这里是我们要开发的目录&#xff0c;基本上要做的事情都在这个目录里assets放置一些图片&#xff0c;如logo等。componentsvue组件…

加快edge网页的下载速度

1、 edge://flags/#enable-parallel-downloading、 2、 点击enabled

Leetcode每日一题:1448. 统计二叉树中好节点的数目(2023.8.25 C++)

目录 1448. 统计二叉树中好节点的数目 题目描述&#xff1a; 实现代码与解析&#xff1a; dfs 原理思路&#xff1a; 1448. 统计二叉树中好节点的数目 题目描述&#xff1a; 给你一棵根为 root 的二叉树&#xff0c;请你返回二叉树中好节点的数目。 「好节点」X 定义为&…

谈谈子网划分的定义、作用、划分方式以及案例

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、子网划分的定义 二、子网掩码的作用 1、…

[管理与领导-51]:IT基层管理者 - 8项核心技能 - 6 - 流程

前言&#xff1a; 管理者存在的价值就是制定目标&#xff0c;即目标管理、通过团队&#xff08;他人&#xff09;拿到结果。 要想通过他人拿到结果&#xff1a; &#xff08;1&#xff09;目标&#xff1a;制定符合SMART原则的符合业务需求的目标&#xff0c;团队跳一跳就可以…

【mysql是怎样运行的】-EXPLAIN详解

文章目录 1.基本语法2. EXPLAIN各列作用1. table2. id3. select_type4. partitions5. type 1.基本语法 EXPLAIN SELECT select_options #或者 DESCRIBE SELECT select_optionsEXPLAIN 语句输出的各个列的作用如下&#xff1a; 列名描述id在一个大的查询语句中每个SELECT关键…

Unity——后期处理举例

Post Processing&#xff08;后期处理&#xff09;并不属于特效&#xff0c;但现代的特效表现离不开后期处理的支持。本文以眩光&#xff08;Bloom&#xff09;为例&#xff0c;展示一种明亮的激光的制作方法 1、安装后期处理扩展包 较新的Unity版本已经内置了新版的后期处理扩…