【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.hqwc.cn/news/89991.html

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

相关文章

kafka架构和原理详解

Apache Kafka 是一个分布式流数据平台,用于高吞吐量、持久性、可扩展的发布和订阅消息。它具有高度的可靠性,被广泛用于构建实时数据流处理、日志收集和数据管道等应用。 基本架构 1. 主题(Topic): 主题是消息的逻辑分类生产者将消息发布到特定的主题中,而消费者可以订阅…

gitlab提交项目Log in with Access Token错误

目录 报错信息 问题描述 解决方案 报错信息 问题描述 在提交项目到gitlab时&#xff0c;需要添加账户信息 &#xff0c;但是报了这样一个错&#xff0c;原因应该就是路径问题&#xff0c;我在填写server地址的时候&#xff0c;就出现了路径问题&#xff0c;我把多余的几个/…

远传水表数据是怎么远传的?

随着科技的不断发展&#xff0c;智慧城市的建设逐渐成为城市发展的重要方向&#xff0c;而智能水表作为智慧城市中的重要组成部分&#xff0c;它的数据远传功能更是给水务管理带来了极大的便利。下面就由在智能水电表行业摸爬滚打多年的小编来为大家讲解下吧! 一、远传水表数据…

在 Spring Boot 中集成 MinIO 对象存储

MinIO 是一个开源的对象存储服务器&#xff0c;专注于高性能、分布式和兼容S3 API的存储解决方案。本文将介绍如何在 Spring Boot 应用程序中集成 MinIO&#xff0c;以便您可以轻松地将对象存储集成到您的应用中。 安装minio 拉取 minio Docker镜像 docker pull minio/minio创…

正交实验如何进行方差分析?

一、案例介绍 欲研究温度&#xff08;℃&#xff09;、时间&#xff08;min&#xff09;、催化剂含量&#xff08;%&#xff09;对某物质转化率的影响&#xff0c;当前使用正交试验设计进行试验&#xff0c;试探究3个因素对转化率的影响是否显著&#xff0c;并找到能使转化率达…

精读《算法题 - 地下城游戏》

今天我们看一道 leetcode hard 难度题目&#xff1a;地下城游戏。 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士…

设计模式之命令模式(Command)的C++实现

1、命令模式的提出 在软件开发过程中&#xff0c;“行为请求者”和“行为实现者”通常呈现一种“紧耦合”&#xff0c;如果行为的实现经常变化&#xff0c;则不利于代码的维护。命令模式可以将行为的请求者和行为的实现者进行解耦。具体流程是将行为请求者封装成一个对象&…

vulhub之MinIO信息泄露漏洞(CVE-2023-28432)

文章目录 0x01 前言0x02 漏洞描述0x03 影响范围0x04 漏洞复现1.启动环境2.查看端口3.构造POC 0x05 修复建议 0x01 前言 本次测试仅供学习使用&#xff0c;如若非法他用&#xff0c;与本文作者无关&#xff0c;需自行负责&#xff01;&#xff01;&#xff01; 0x02 漏洞描述 …

多线程应用——单例模式

单例模式 文章目录 单例模式一.什么是单例模式二.如何实现1.口头实现2.利用语法特性 三.实现方式&#xff08;饿汉式懒汉式&#xff09;1.饿汉式2.懒汉式3.线程安全的单例模式4.双重检查锁5.禁止指令重排序 一.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff…

Vue.js2+Cesium1.103.0 十一、Three.js 炸裂效果

Vue.js2Cesium1.103.0 十一、Three.js 炸裂效果 Demo ThreeModelBoom.vue <template><div:id"id"class"three_container"/> </template><script> /* eslint-disable eqeqeq */ /* eslint-disable no-unused-vars */ /* eslint-d…

使用U盘重装Windows10系统详细步骤及配图【官方纯净版】

文章目录 1.制作启动盘1.1准备U盘及一台电脑1.2下载win10安装包 2.安装操作系统2.1插入系统安装盘2.2设置启动盘为第一启动项2.3开始安装操作系统 3.安装成功后进入图形界面3.1启动问题3.2驱动问题3.3调出"控制面板"3.4给磁盘分区 4.win10激活 前天下午不知道怎么想的…

LeetCode--HOT100题(45)

目录 题目描述&#xff1a;199. 二叉树的右视图&#xff08;中等&#xff09;题目接口解题思路 PS: 题目描述&#xff1a;199. 二叉树的右视图&#xff08;中等&#xff09; 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序…