Menu
CoddyTech

可变参数模板

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 67 课,共 104 课。

如果你需要一个可以接受任意数量、任意类型参数的函数,该怎么办?传统模板要求你明确指定所需的类型参数数量。可变参数模板解决了这个问题,它允许模板接受任意数量的模板参数。

这种语法使用 ...(省略号)来创建参数包:

template <typename... Args>
void printAll(Args... args) {
    // Args 是模板参数包
    // args 是函数参数包
}

要处理这些参数,通常可以使用带有基准情况的递归。参数包每次展开一个参数,直到没有剩余参数:

// 基本情况:没有剩余参数
void print() {
    std::cout << std::endl;
}

// 递归情况:处理第一个参数,再用剩余参数递归调用
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);  // 展开剩余参数
}

print(1, 3.14, "hello", 'x');  // 输出:1 3.14 hello x

你还可以使用折叠表达式(C++17)执行更简单的操作,无需显式递归:

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 折叠表达式:将所有参数相加
}

std::cout << sum(1, 2, 3, 4) << std::endl;  // 输出:10

可变参数模板为许多标准库功能提供支持,例如 std::make_unique、std::tuple 和 std::function。它们可以实现类型安全的函数,让函数能够处理任意组合、任意数量的参数类型。

challenge icon

挑战

简单

让我们构建一个灵活的日志系统,使用可变参数模板处理任意数量参数的消息。你将创建一些工具,用于拼接值、统计参数数量并输出格式化内容,全部通过参数包实现。

你将把代码分成两个文件:

  • Variadic.h:在这里定义可变参数模板函数。

    创建一个 printAll 函数,用空格分隔并输出所有参数,最后换行。使用递归方式,基础情况只输出换行,递归情况输出第一个参数和一个空格,然后对剩余参数继续递归。

    创建一个 countArgs 函数,返回传入的参数数量。使用 sizeof... 获取参数包的大小。

    创建一个 sum 函数,将所有数值参数相加并返回结果。使用带有 + 运算符的折叠表达式。

    创建一个 product 函数,将所有数值参数相乘并返回结果。使用带有 * 运算符的折叠表达式。

  • main.cpp:读取 4 个输入(每个输入占一行):
    1. 一个整数
    2. 一个 double 值
    3. 一个字符串
    4. 一个字符

    通过以下方式演示你的可变参数模板:

    1. 使用全部 4 个输入调用 printAll 并输出结果
    2. 使用整数、double 值和字符调用 countArgs,并输出参数数量:Argument count: <count>
    3. 将整数、double 值以及数值 10 和 5 相加,然后输出:Sum: <result>
    4. 将整数以及数值 2 和 3 相乘,然后输出:Product: <result>
    5. 只使用字符串(单个参数)调用 printAll
    6. 不带参数调用 printAll(应只输出一个换行符)

例如,输入 5、2.5、Hello 和 X 时:

5 2.5 Hello X 
Argument count: 3
Sum: 22.5
Product: 30
Hello 

注意,printAll 可以处理任意数量、类型各异的参数,从 4 个参数到 0 个参数都可以。sum 和 product 函数使用折叠表达式来简洁地组合所有值,无需显式递归。你的 countArgs 函数则演示了 sizeof... 如何在编译时获取参数包的大小。

亲手试试

#include <iostream>
#include <string>
#include "Variadic.h"

using namespace std;

int main() {
    // 读取输入
    int intVal;
    double doubleVal;
    string strVal;
    char charVal;
    
    cin >> intVal;
    cin >> doubleVal;
    cin >> strVal;
    cin >> charVal;
    
    // TODO: 将全部 4 个输入传给 printAll 并调用它
    
    // TODO: 将 intVal、doubleVal 和 charVal 传给 countArgs,输出参数数量
    // 格式:"Argument count: <count>"
    
    // TODO: 计算并输出 intVal、doubleVal、10 和 5 的和
    // 格式:"Sum: <result>"
    
    // TODO: 计算并输出 intVal、2 和 3 的乘积
    // 格式:"Product: <result>"
    
    // TODO: 只传入字符串,调用 printAll
    
    // TODO: 不传入参数,调用 printAll
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器