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

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

前言:

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



序言

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 可变参数模板函数的全部知识讲解!!感谢大家的观看与支持!!!

相关文章
|
16天前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
|
16天前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
|
2月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
92 12
|
3月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
4月前
|
编译器 C++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
4月前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
68 0
|
4月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
4月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
16天前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
|
3月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
78 16

热门文章

最新文章