C与C++中的可变参数
下面先区分一下两种“可变参数”机制,再重点讲讲 C++11 引入的可变模板参数(variadic templates)。
1. C 风格的可变参数(…)
不是的,va_list
本身并不是“可变参数”,而是 C 标准库(<stdarg.h>
)提供的一个类型,用来在函数内部访问由 …
引入的可变参数列表。
具体来说:
- 可变参数函数的声明
这里的void foo(int fixed_arg, …);
…
(省略号)才是告诉编译器“这是个可变参数函数,可以接收不定个数的实参”。
举个最小例子:
#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 基本语法
template<typename… Args>
void foo(Args… args) {// …
}
Args…
:模板参数包(parameter pack)args…
:对应的函数形参包
你可以在函数体里用**展开(expansion)**把 args…
拆成一堆独立的参数。
2.2 递归展开(C++11/14)
演示一个打印所有参数的例子:
#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');
}
思路:
- 至少拆出一个参数
first
,剩下的仍是包rest…
- 递归调用直到
rest…
为空
2.3 折叠表达式(Fold Expressions,C++17)
C++17 提供更简洁的“折叠表达式”来一次性展开参数包:
#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 完美转发
#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。