C++的...符号(可变参数实现)

在 C++ 中实现可变参数有多种方法,我为你详细介绍几种主要的方式:

1. C 风格可变参数(不推荐)

cpp 复制代码
#include <cstdarg>
#include <iostream>

void printNumbers(int count, ...) {
    va_list args;
    va_start(args, count);
    
    for (int i = 0; i < count; i++) {
        int num = va_arg(args, int);
        std::cout << num << " ";
    }
    
    va_end(args);
    std::cout << std::endl;
}

// 使用
printNumbers(3, 1, 2, 3);  // 需要手动指定参数个数

缺点:类型不安全,需要手动管理参数,容易出错。

2. 可变参数模板(C++11 推荐)

这是最现代、最安全的方式。

基本用法

cpp 复制代码
#include <iostream>

// 递归终止函数
void print() {
    std::cout << std::endl;
}

// 可变参数模板函数
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用
}

// 使用
print(1, 2.5, "hello", 'A');  // 输出:1 2.5 hello A

使用折叠表达式(C++17)

cpp 复制代码
template<typename... Args>
void print(Args&&... args) {
    ((std::cout << args << " "), ...);  // 折叠表达式
    std::cout << std::endl;
}

3. std::initializer_list(固定类型)

cpp 复制代码
#include <iostream>
#include <initializer_list>

void print(std::initializer_list<int> values) {
    for (auto val : values) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

// 使用
print({1, 2, 3, 4, 5});

4. 实际应用示例

实现格式化字符串

cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>

template<typename... Args>
std::string format(const std::string& fmt, Args... args) {
    char buffer[256];
    snprintf(buffer, sizeof(buffer), fmt.c_str(), args...);
    return std::string(buffer);
}

// 使用
auto str = format("Name: %s, Age: %d, Score: %.2f", "Alice", 25, 95.5);

通用求和函数

cpp 复制代码
#include <iostream>

// C++17 折叠表达式
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 右折叠
}

// C++11/14 版本
template<typename T>
T sum(T t) {
    return t;
}

template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

// 使用
auto total = sum(1, 2, 3, 4, 5);  // 15
auto total2 = sum(1.1, 2.2, 3.3); // 6.6

创建任意参数的类

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

class Logger {
public:
    template<typename... Args>
    void log(Args... args) {
        std::ostringstream oss;
        (oss << ... << args);  // C++17 折叠表达式
        logs.push_back(oss.str());
    }
    
    void showLogs() {
        for (const auto& log : logs) {
            std::cout << log << std::endl;
        }
    }

private:
    std::vector<std::string> logs;
};

// 使用
Logger logger;
logger.log("Error", " at line ", 25, ", value: ", 3.14);
logger.log("User", " ", "Alice", " logged in");

5. 参数包展开技巧

cpp 复制代码
#include <iostream>

// 1. 递归展开
template<typename T>
void process(T t) {
    std::cout << t << std::endl;
}

template<typename T, typename... Args>
void process(T t, Args... args) {
    std::cout << t << ", ";
    process(args...);
}

// 2. 逗号运算符展开
template<typename... Args>
void printAll(Args... args) {
    int dummy[] = { (std::cout << args << " ", 0)... };
    std::cout << std::endl;
}

// 3. Lambda 表达式展开(C++14+)
template<typename... Args>
void processWithLambda(Args... args) {
    ([](auto arg) {
        std::cout << arg << " ";
    }(args), ...);
    std::cout << std::endl;
}

6. 类型安全的变体

cpp 复制代码
#include <iostream>
#include <variant>
#include <vector>

using VariantParam = std::variant<int, double, std::string>;

void processVariadic(const std::vector<VariantParam>& params) {
    for (const auto& param : params) {
        std::visit([](auto&& arg) {
            std::cout << arg << " ";
        }, param);
    }
    std::cout << std::endl;
}

// 使用
processVariadic({1, 3.14, "hello", 42});

选择建议

  1. C++11 及以上:优先使用可变参数模板,类型安全且灵活
  2. 同类型参数 :考虑使用 std::initializer_list
  3. C++17:使用折叠表达式简化代码
  4. 避免:C 风格的可变参数,除非需要与 C 代码交互

可变参数模板是 C++ 中实现可变参数最强大和安全的方式,虽然语法看起来复杂,但提供了完全的编译时类型检查。

问得很好!这是理解可变参数模板的关键点。... 的位置很重要,让我详细解释:

... 的三个位置及含义

1. 模板参数声明中:typename... Args

cpp 复制代码
template<typename... Args>  // ✔ 正确:声明参数包
template<typename Args...>  // ❌ 错误:...必须在类型名后面

这里的 ... 表示 声明一个模板参数包,可以接受任意数量的类型参数。

2. 函数参数声明中:Args... args

cpp 复制代码
template<typename... Args>
void func(Args... args) {  // ✔ 正确:声明函数参数包
    // ...
}

template<typename... Args>
void func(args... Args) {  // ❌ 错误:参数名在前,包名在后
    // ...
}

3. 展开参数包时:args...

cpp 复制代码
template<typename... Args>
void func(Args... args) {
    // 展开参数包
    otherFunc(args...);  // ✔ 正确:展开所有参数
    
    // 部分展开
    otherFunc(args...);      // 展开所有
    otherFunc(args)...;      // ❌ 错误:语法不对
}

完整的位置示例

cpp 复制代码
// 声明处:typename... 在类型前面
template<typename... Types>  // Types 是类型参数包

// 函数参数处:Types... 在变量名前面
void process(Types... values) {  // values 是函数参数包
    
    // 使用处:values... 在函数调用中展开
    print(values...);  // 展开所有参数
    
    // 或者用递归方式
    print(values...);  // 正确
}

具体使用场景对比

场景1:直接传递所有参数

cpp 复制代码
template<typename... Args>
void forward(Args... args) {
    // args... 在函数调用内部
    targetFunction(args...);  // 展开所有参数
}

场景2:递归处理(C++11/14风格)

cpp 复制代码
// 基本情况
void print() {
    std::cout << std::endl;
}

// 递归情况:第一个参数在前,剩余参数包在后
template<typename T, typename... Args>
void print(T first, Args... rest) {  // first 是单个参数,rest 是参数包
    std::cout << first << " ";
    print(rest...);  // 展开剩余参数
}

场景3:折叠表达式(C++17)

cpp 复制代码
template<typename... Args>
void print(Args... args) {
    // ... 在表达式中间
    ((std::cout << args << " "), ...);  // 右折叠
    // 或者
    (..., (std::cout << args << " "));  // 左折叠
    
    std::cout << std::endl;
}

常见的 ... 位置模式

cpp 复制代码
// 1. 模板声明:typename... 包名
template<typename... Args>

// 2. 函数声明:包名... 参数名
ReturnType functionName(Args... args)

// 3. 函数调用:参数名...
otherFunction(args...);

// 4. 递归调用:参数包...
function(rest...);

// 5. 折叠表达式:(表达式 ...) 或 (... 表达式)
(args + ...)           // 右折叠:arg1 + (arg2 + (arg3 + arg4))
(... + args)           // 左折叠:(((arg1 + arg2) + arg3) + arg4)

记忆口诀

  1. 声明时typename... 在类型前面
  2. 参数时Args... 在变量名前面
  3. 使用时args... 作为整体展开

错误示例分析

cpp 复制代码
// ❌ 错误:... 位置不对
template<typename Args...>      // 应该在typename后面,Args前面
void func(...Args args)         // 应该在Args后面,args前面
void func(args... Args)         // 完全反了
otherFunc(...args);             // 不应该有单独的...
otherFunc(args...);             // ✔ 正确:展开所有参数

实际应用示例

cpp 复制代码
#include <iostream>
#include <tuple>

// 正确示例
template<typename... Args>
auto make_tuple(Args... args) {  // Args... args ✔
    return std::make_tuple(args...);  // args... ✔
}

// 转发示例
template<typename... Args>
void wrapper(Args&&... args) {  // Args&&... args ✔
    // 完美转发
    target(std::forward<Args>(args)...);  // args... ✔
}

int main() {
    // 使用
    auto t = make_tuple(1, 3.14, "hello");  // 自动推导Args...
    
    // 查看结果
    std::cout << std::get<0>(t) << " "
              << std::get<1>(t) << " "
              << std::get<2>(t) << std::endl;
    
    return 0;
}

简单记法 :在声明时,... 紧跟在 typenameArgs 后面;在使用时,... 紧跟在参数包名字后面。

相关推荐
lsx2024061 小时前
MySQL LIKE 子句详解
开发语言
点云SLAM1 小时前
C++ 右值引用(rvalue references)与移动语义(move semantics)深度详解
开发语言·c++·右值引用·移动语义·c++17·c+高级应用·代码性能优化
Q一件事1 小时前
R语言中的图片布局设置
开发语言·r语言
南猿北者1 小时前
go环境搭建--Linux
linux·开发语言·golang
云和数据.ChenGuang4 小时前
Ascend C 核心技术特性
c语言·开发语言
kyle~7 小时前
C++---value_type 解决泛型编程中的类型信息获取问题
java·开发语言·c++
NiNi_suanfa10 小时前
【Qt】Qt 批量修改同类对象
开发语言·c++·qt
小糖学代码10 小时前
LLM系列:1.python入门:3.布尔型对象
linux·开发语言·python
Data_agent10 小时前
1688获得1688店铺详情API,python请求示例
开发语言·爬虫·python