- 可变参数
- C风格的可变参数
- C风格可变参数的使用
- C++11可变参数模板
- 递归展开参数包
- 参数列表展开
- 折叠表达式
- STL中的emplace插入接口
可变参数
C风格的可变参数
可变参数是一种语言特性,可以在函数声明中使用省略号...
来表示函数接受可变数量的参数。
例如典型的printf()
函数:
int printf (const char * szFormat, ...);
// printf("%d + %d = %d \n", 1, 2, 1+2);
第一个参数是格式化字符串,后面的...
是可变参数
//使用宏定义与可变参数
//__VA_ARGS__表示宏定义中的可变参数部分
#define LOG(format, ...) printf(format, ##__VA_ARGS__)
如果可变参数列表为空,##
会删除前面的逗号,避免语法错误
C风格可变参数的使用
在函数体内,可以使用 va_list 类型的变量和va_start, va_arg, va_end,函数来处理可变参数。
在 <stdarg.h> 头文件中包含
paramN : 函数定义中最后一个命名参数的名称。随后调用va_arg提取的参数是后面的参数
示例:
void my_printf(const char* format,...)
{
//定义一个va_list类型变量
va_list argList;
//初始化argList,获取format后面的额外参数
va_start(argList, format);
//依次获取可变的参数值。需要指定参数值的数据类型
while (*format != '\0') {
if(*format == '%') {
format++;
switch (*format) {
case 'd': //整数
cout << va_arg(argList, int) << " ";
break;
case 'f': //浮点数
cout << va_arg(argList, double) << " ";
break;
}
}
format++;
}
//释放va_list变量
va_end(argList);
}
my_printf("%d, %f", 25, 3.22);
C++11可变参数模板
C++11新增特性。可变参数模板是一种用于处理具有不定数量参数的函数模板的特性。
定义:
template<class ...Args>
viod func(Args... args);
Args
为参数包名,前加可变参数...
进行声明。参数包中可以包含0~n个不同类型参数
通过使用参数包和展开表达式,才能获取并使用参数
递归展开参数包
示例:
//当递归展开介绍,调用该终止函数
int sum() {
return 0;
}
// 递归情况:将第一个参数与后面的参数相加
template<typename T, typename... Args>
T sum(T first, Args... rest) {
return first + sum(rest...);
}
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出:15
std::cout << sum(10.5, 20.5, 30.5) << std::endl; // 输出:61.5
使用参数包时,需要加上展开操作符
...
递归展开时,需要注意,如果没有终止函数,当参数包中的参数个数为0后,会发生死循环
上述代码的终止函数也可以写一下形式:
//当没有参数时,返回0
template<typename T>
T sum(T value) {
return value;
}
参数列表展开
通过参数列表(initializer_list)获取参数包中的参数
如果参数包中的各个参数都是相同类型,这可以通过列表初始化的形式,将其转移到数组中。
template<typename... Args>
void func(Args... args) {
int dummy[] = { args...};
dummy[0];//使用
...
}
该操作,不支持0个参数的参数包(不能分配常量大小为0的数组)
对于不同类型的参数,可以利用参数列表结合逗号表达式,将参数包展开为逗号分隔的一系列独立的参数。
template<class T>
void doSomething(const T& t)
{
...//使用
}
template<typename... Args>
void myFunction(Args... args) {
// 使用展开操作符将参数包展开
int arr[] = { (doSomething(args), 0)...};
// ...
}
通过初始化列表来初始化一个变长数组,然后再对数组arr
初始化时,会执行expand函数中的逗号表达式(从左到右计算表达式并将最后一个表达式的值返回)。最终会在创建完一个int arr[sizof ...args] = {0}
的数组同时,处理参数包中的参数。
当然这里的int
型以及逗号表达式中的0
值,都没有后续的使用意义。
myFunction(1, 2.5, "hello");
//参数包展开为:
int arr[] = {
(doSomething(1), 0),
(doSomething(2.5), 0),
(doSomething("hello"), 0)
};
如果不想在0个参数时
myFunction();
,编译报错,可增加无参的重载
//可增加一个无参的函数重载,来对0个参数特殊处理
void doSomething();
折叠表达式
折叠表达式(Fold Expressions) 是 C++17 引入的一种简化可变参数模板(variadic templates)的语法特性。
有四种:
(pack op ...) // 一元左折叠
(... op pack) // 一元右折叠
(init op ... op pack) // 二元左折叠
(pack op ... op init) // 二元右折叠
使用:
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元左折叠
}
sum(1,2,3,4); // (((1 + 2) + 3) + 4)
template<typename... Args>
auto sum(Args... args) {
return (... + args); // 一元右折叠
}
sum(1,2,3,4); // (1 + (2 + (3 + 4)))
template<typename... Args>
auto sum_with_init(int init, Args... args) {
return (init + ... + args); // 二元左折叠
}
sum_with_init(0,1,2,3); // (((0 + 1) + 2) + 3)
template<typename... Args>
auto sum_with_init(int init, Args... args) {
return (args + ... + init); // 二元右折叠
}
sum_with_init(0,1,2,3); // (1 + (2 + (3 + 10)))
template<typename... Args>
void myFunction(Args... args) {
(doSomething(args), ...); // 折叠表达式
}
myFunction(1, 2.5, "hello");
// (doSomething(1), (doSomething(2.5), (doSomething("hello"))));
- pack :
args
- op :
,
STL中的emplace插入接口
例如在vector
容器中:
该成员函数,就是使用了可变参数模板,使用上和原来的插入接口push_back
差别不大
vector<std::pair<int, string>> v;
v.push_back(make_pair( 0, "hello" ));
v.emplace_back(1, "world");
make_pair会多构建一次string对象的消耗
🦀🦀观看~~