可变参数模板
本课属于 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。它们可以实现类型安全的函数,让函数能够处理任意组合、任意数量的参数类型。
挑战
简单让我们构建一个灵活的日志系统,使用可变参数模板处理任意数量参数的消息。你将创建一些工具,用于拼接值、统计参数数量并输出格式化内容,全部通过参数包实现。
你将把代码分成两个文件:
Variadic.h:在这里定义可变参数模板函数。创建一个
printAll函数,用空格分隔并输出所有参数,最后换行。使用递归方式,基础情况只输出换行,递归情况输出第一个参数和一个空格,然后对剩余参数继续递归。创建一个
countArgs函数,返回传入的参数数量。使用sizeof...获取参数包的大小。创建一个
sum函数,将所有数值参数相加并返回结果。使用带有+运算符的折叠表达式。创建一个
product函数,将所有数值参数相乘并返回结果。使用带有*运算符的折叠表达式。main.cpp:读取 4 个输入(每个输入占一行):- 一个整数
- 一个 double 值
- 一个字符串
- 一个字符
通过以下方式演示你的可变参数模板:
- 使用全部 4 个输入调用
printAll并输出结果 - 使用整数、double 值和字符调用
countArgs,并输出参数数量:Argument count: <count> - 将整数、double 值以及数值 10 和 5 相加,然后输出:
Sum: <result> - 将整数以及数值 2 和 3 相乘,然后输出:
Product: <result> - 只使用字符串(单个参数)调用
printAll - 不带参数调用
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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器