C与C++中的可变参数

下面先区分一下两种"可变参数"机制,再重点讲讲 C++11 引入的可变模板参数(variadic templates)。


1. C 风格的可变参数(...)

不是的,va_list 本身并不是"可变参数",而是 C 标准库(<stdarg.h>)提供的一个类型 ,用来在函数内部访问由 ... 引入的可变参数列表。

具体来说:

  1. 可变参数函数的声明

    c 复制代码
    void foo(int fixed_arg, ...);

    这里的 ...(省略号)才是告诉编译器"这是个可变参数函数,可以接收不定个数的实参"。

举个最小例子:

c 复制代码
#include <stdio.h>
#include <stdarg.h>

void my_printf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt, ap);   // 标准库 vprintf 就是接收 va_list 的版本
    va_end(ap);
}

int main() {
    my_printf("x=%d, y=%.2f\n", 42, 3.14);
    return 0;
}
  • ...:声明可变参数
  • va_list ap;:申明一个遍历句柄
  • va_start(ap, fmt);:指向第 1 个可变参数
  • va_arg(ap, Type):依次读取
  • va_end(ap);:结束

总结:

  • "可变参数"是函数签名里的 ...
  • va_list 是访问这些参数的工具类型。

缺点:

  • 不做类型检查,容易出错
  • 访问时需要 va_arg 按正确类型一一取出

2. C++11 可变模板参数(Variadic Templates)

2.1 基本语法

cpp 复制代码
template<typename... Args>
void foo(Args... args) {
    // ...
}
  • Args...:模板参数包(parameter pack)
  • args...:对应的函数形参包

你可以在函数体里用**展开(expansion)**把 args... 拆成一堆独立的参数。

2.2 递归展开(C++11/14)

演示一个打印所有参数的例子:

cpp 复制代码
#include <iostream>

// 终止版本
void print_all() {
    std::cout << "\n";
}

// 递归版本
template<typename T, typename... Ts>
void print_all(T first, Ts... rest) {
    std::cout << first;
    if constexpr (sizeof...(rest) > 0) {
        std::cout << ", ";
        print_all(rest...);  // 递归展开
    } else {
        std::cout << "\n";
    }
}

int main() {
    print_all(1, "hello", 3.14, 'x');
}

思路:

  1. 至少拆出一个参数 first,剩下的仍是包 rest...
  2. 递归调用直到 rest... 为空

2.3 折叠表达式(Fold Expressions,C++17)

C++17 提供更简洁的"折叠表达式"来一次性展开参数包:

cpp 复制代码
#include <iostream>

template<typename... Ts>
void print_all(Ts const&... args) {
    // ( expr ⌄ ... ) 或 ( ... ⌄ expr ) 形式
    // 这里用逗号运算符,最后再输出换行
    ((std::cout << args << ", "), ...);
    std::cout << "\n";
}

int main() {
    print_all(1, "foo", 2.5, 'c');
}

常见折叠用法举例:

  • 求和: auto sum = (args + ... + 0);
  • 逻辑与: auto all_ok = (args && ...);
  • 输出: (std::cout << ... << args);

2.4 完美转发

cpp 复制代码
#include <utility>

template<typename... Ts>
void logf(const char* fmt, Ts&&... args) {
    // 假设你有个 format_to_string(fmt, ...) 函数
    std::string s = format_to_string(fmt, std::forward<Ts>(args)...);
    OutputDebugStringA(s.c_str());
}
  • Ts&&... args:参数包完美转发
  • std::forward<Ts>(args)...:保留左值/右值属性

3. 小结

  • C++ 的 ...<cstdarg>)是 C 时代遗留的,类型不安全
  • Variadic Templates (C++11 起)提供了类型安全编译期展开的可变参数
  • 配合递归、折叠表达式和完美转发,能写出既灵活又高效的通用 API。
相关推荐
kruptos2 小时前
分布式系统怎么“选主“?Raft 共识一次讲清
开发语言·分布式·php·共识算法
吴声子夜歌3 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
benchmark_cc3 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
王的宝库4 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
Tairitsu_H4 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
泡海椒4 小时前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
小智老师PMP4 小时前
2026深度解析|PMP第八版与NPDP核心侧重点本质区别(管理类证书怎么选)
开发语言·分布式·算法·职场和发展·产品经理
HugoStudio_SWAN4 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活
江畔柳前堤5 小时前
前台·中台·后台:2026年AI原生时代的架构全景图
开发语言·人工智能·算法·机器学习·架构·scala·ai-native
Figo_Cheung5 小时前
Figo基于RNC理论的宇宙演化第七纪元猜想
开发语言·php