C++11可变参数模板(typename… Args模板参数包或class… Args)(Args… args函数参数包)(递归展开与参数包展开(只支持C++17))

文章目录

    • C++可变参数
    • C++可变参数模板
      • 示例(可变参数模板、递归参数模板、C++17折叠表达式)
    • 可变参数模板的使用方式有两种,递归展开和参数包展开
      • 递归展开
      • 参数包展开(只支持C++17)
    • 为什么`template `模板参数中`…`放`Args`前面,`void printSizeOfArgs(Args… args)`中`…`放`Args`后面?
    • 模板参数和函数参数有什么区别?

C++可变参数

C++可变参数是指函数的参数个数是可变的,可以在函数定义时不确定参数的个数,需要在函数体内通过特定的语法来处理这些参数。C++11标准引入了新的可变参数模板,使得可变参数的处理更加方便和灵活。在函数定义时,可以使用省略号(…)来表示可变参数,然后通过va_list、va_start、va_arg和va_end等宏来访问这些参数。例如,下面是一个简单的可变参数函数的示例:

#include 
#include 

using namespace std;

double average(int count, ...)
{
    va_list ap;
    int i;
    double sum = 0;

    va_start(ap, count); // 初始化可变参数列表

    for (i = 0; i < count; i++)
    {
        sum += va_arg(ap, double); // 访问可变参数列表中的参数
    }

    va_end(ap); // 结束可变参数列表

    return sum / count;
}

int main()
{
    cout << average(3, 1.0, 2.0, 3.0) << endl; // 输出平均值
    return 0;
}

在这里插入图片描述

在上面的示例中,average函数的第一个参数count表示可变参数的个数,后面的省略号表示可变参数列表。在函数体内,首先使用va_start宏初始化可变参数列表,然后使用va_arg宏访问可变参数列表中的参数,最后使用va_end宏结束可变参数列表。

C++可变参数具体可参见:C语言函数参数中的三个点(三点 “…”)是干什么用的?(可变参数)<stdarg.h>、va_start 宏、va_arg 宏、va_end 宏

C++可变参数模板

C++11标准引入了新的可变参数模板,使得可变参数的处理更加方便和灵活。可变参数模板是指模板参数个数是可变的,可以在模板定义时不确定参数的个数,需要在模板实例化时通过特定的语法来处理这些参数。

可变参数模板的语法形式为:

template 
void func(Args... args)
{
    // 函数体
}

其中,typename… Args表示模板参数包,可以包含任意个数的模板参数,args表示函数参数包,可以包含任意个数的函数参数。在函数体内,可以使用sizeof…(Args)来获取模板参数包中参数的个数,使用sizeof…(args)来获取函数参数包中参数的个数。

示例(可变参数模板、递归参数模板、C++17折叠表达式)

#include 
#include 
#include 
using namespace std;

// ------------------------------------------------------------C++17折叠表达式

template 
void foldPrint(Args... args)
{
    std::cout << "The number of template arguments is: " << sizeof...(Args) << std::endl; // The number of template arguments is: 4
    std::cout << "The number of function arguments is: " << sizeof...(args) << std::endl; // The number of arguments is: 4

#if 0
    // C++17折叠表达式对可变函数参数的处理
    std::cout << "The arguments are: ";
    (std::cout << ... << args) << std::endl; // The arguments are: 12helloc
#endif
}

// ------------------------------------------------------------递归函数模板

// 基本模板函数
template 
void recursionPrint(T t)
{
    std::cout << t << std::endl;
}

// 特化版本,用于处理空参数包的情况
// template 
// void recursionPrint(int t)
// {
//     std::cout << t << std::endl;
// }

// 递归模板函数(当参数还剩一个时,就会调到上面那个基本模板函数那儿)
template 
void recursionPrint(T t, Args... args)
{
    std::cout << t << ", "; // 不endl不会刷新缓冲区
    recursionPrint(args...);
}

// ------------------------------------------------------------C++11 可变参数模板和 C++17 折叠表达式

template 
void variablePrint(Args... args)
{
    int arr[] = {(std::cout << args << ", ", 0)...}; // 这段代码是使用了 C++11 中的可变参数模板和折叠表达式,可以将多个参数打印出来并存储在数组中。
    // 具体来说,代码中的 args 是一个可变参数模板,表示可以接受任意数量的参数。
    //(std::cout << args << ", ", 0) 是一个折叠表达式,表示对于每个参数,先将其输出到标准输出流 std::cout 中,然后再输出一个逗号和一个空格,并返回 0。
    // 这样,对于每个参数,都会输出到标准输出流中,并返回 0。
    // 最后,使用花括号将所有参数的输出结果存储在一个数组 arr 中,这个数组的类型是 int[],因为折叠表达式中返回的是 0,所以数组中的每个元素都是 0。
    // 需要注意的是,这段代码的输出结果中,每个参数之间都会有一个逗号和一个空格,最后一个参数后面也会有一个逗号和一个空格。
    // 如果需要去掉最后一个参数后面的逗号和空格,可以在输出数组元素时进行特判。
    std::cout << std::endl;
}

int main()
{
    // C++17折叠表达式
    std::cout << "C++17折叠表达式" << std::endl;
    foldPrint(1, 2.0, "hello", 'c');
    std::cout << std::endl;

    // 递归函数模板
    std::cout << "递归函数模板" << std::endl;
    recursionPrint(1, 2.0, "hello", 'c'); // 1, 2, hello, c
    std::cout << std::endl;

    // 可变参数模板
    std::cout << "C++11 可变参数模板和 C++17 折叠表达式" << std::endl;
    variablePrint(1, 2.0, "hello", 'c'); // 1, 2, hello, c,
    std::cout << std::endl;

    return 0;
}

运行结果:

在这里插入图片描述

可变参数模板的使用方式有两种,递归展开和参数包展开

递归展开

递归展开是指在函数模板内部使用递归调用来处理可变参数,直到处理完所有参数。例如,下面是一个递归展开可变参数模板的示例:

#include 
#include 
#include 
using namespace std;

template 
void print(T t)
{
    cout << t << endl;
}

template 
void print(T t, Args... args)
{
    cout << t << ", ";
    print(args...);
}

int main()
{
    print(1, 2.0, "hello"); // 输出1, 2, hello
    return 0;
}

在这里插入图片描述

在上面的示例中,print函数模板使用递归调用来处理可变参数,首先输出第一个参数t,然后递归调用print函数模板来处理剩余的参数args。

参数包展开(只支持C++17)

参数包展开是指使用特定的语法来展开参数包,将参数包中的参数逐个传递给函数。例如,下面是一个参数包展开可变参数模板的示例:

#include 
#include 
#include 
using namespace std;

#include 

using namespace std;

template 
void print(Args... args)
{
    cout << sizeof...(args) << endl; // 输出参数个数
    (cout << ... << args) << endl;   // 展开参数包
}

int main()
{
    print(1, 2.0, "hello"); // 输出3和12hello
    return 0;
}

在这里插入图片描述

在上面的示例中,print函数模板使用sizeof…(args)来获取参数个数,使用(cout << … << args)来展开参数包,将参数包中的参数逐个传递给cout输出。

为什么template 模板参数中…放Args前面,void printSizeOfArgs(Args… args)中…放Args后面?

template 中的…放在Args前面是因为它表示一个模板参数包,用于接收任意数量的模板参数。而void printSizeOfArgs(Args… args)中的…放在Args后面是因为它表示一个函数参数包,用于接收任意数量的函数参数。

在模板参数列表中,…表示一个参数包,它可以接收任意数量的模板参数。例如,template 中的Args可以接收任意数量的模板参数,比如Args、Args、Args等等。

在函数参数列表中,…表示一个参数包,它可以接收任意数量的函数参数。例如,void printSizeOfArgs(Args… args)中的args可以接收任意数量的函数参数,比如printSizeOfArgs(1)、printSizeOfArgs(1, 2, 3)、printSizeOfArgs(“hello”, ‘a’, 3.14)等等。

因此,…放在Args前面和后面的含义是不同的,它们分别表示模板参数包和函数参数包。

模板参数和函数参数有什么区别?

模板参数和函数参数的区别主要有以下几点:

  1. 类型不同

模板参数是用于定义模板的类型、常量或模板的类型参数,它们是在编译时确定的。而函数参数是用于传递函数调用时的实际参数,它们是在运行时确定的。

  1. 作用域不同

模板参数的作用域是整个模板,可以在模板的任何地方使用。而函数参数的作用域只在函数内部有效,不能在函数外部使用。

  1. 默认值不同

模板参数可以有默认值,如果没有显式指定模板参数的值,编译器会使用默认值。而函数参数也可以有默认值,但是只能在函数声明中指定,不能在函数定义中指定。

  1. 数量不同

模板参数的数量可以是任意的,可以定义任意数量的模板参数。而函数参数的数量是固定的,必须在函数声明中指定。

  1. 实例化不同

模板参数是在模板实例化时确定的,每个模板实例化都会生成一个新的类型或函数。而函数参数是在函数调用时确定的,每次函数调用都会使用相同的函数定义,但是传递的参数可以不同。

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/38e26aa80c.html