目录
引言
库的基本功能
va_start 宏:
va_arg 宏
va_end 宏
va_copy 宏
使用 处理可变参数代码
C++11可变参数模板
基本概念
sizeof... 运算符
包扩展
引言
在C++编程中,处理不确定数量的参数是一个常见的需求。为了支持这种需求,C标准库提供了 <stdarg.h>
头文件,其中定义了一组宏和类型,用于处理不定参数函数。C++继承了C语言的可变参数机制,使用了stdarg.h
提供的宏来处理不确定数量的参数。其原理基于栈的推入和弹出过程,不需要明确参数数量。此外,C++提供了可变参数机制,让我们能够创建接收任意数量参数的函数。这一特性在许多实际应用中非常有用,比如日志记录、函数重载等。
<stdarg.h>
库的基本功能
<stdarg.h>
库包含以下主要部分:
va_start
宏:
用于初始化 va_list
变量,其基本语法如下:
void va_start(va_list ap, last);
ap
:va_list
变量。last
:最后一个确定的参数,后面的参数是可变参数。
va_arg
宏
va_arg
宏用于访问可变参数列表中的下一个参数,其基本语法如下:
type va_arg(va_list ap, type);
ap
:va_list
变量。type
:要访问的参数的类型。
va_end 宏
va_end 宏用于结束 va_list 变量的访问,其基本语法如下:
void va_end(va_list ap);
- ap:va_list 变量。
va_copy 宏
va_copy 宏用于复制 va_list 变量,其基本语法如下:
void va_copy(va_list dest, va_list src);
- dest:目标 va_list 变量。
- src:源 va_list 变量。
使用 <stdarg.h>
处理可变参数代码
示例中,print_args
接收一个格式字符串,然后根据格式字符(i
表示整数,d
表示双精度浮点数)解析后面的参数。
#include <iostream>
#include <cstdarg>void print_args(const char* fmt, ...)
{va_list args;va_start(args, fmt);while (*fmt != '\0') {if (*fmt == 'i') {int i = va_arg(args, int);std::cout << "int: " << i << std::endl;} elseif (*fmt == 'd') {double d = va_arg(args, double);std::cout << "double: " << d << std::endl;}++fmt;}va_end(args);
}int main()
{print_args("ddii", 0.618,3.14, 7, 9);return 0;
}
//double: 0.618 double: 3.14 int: 7 int: 9
C++11可变参数模板
基本概念
C++11通过模板提供了类型安全且灵活的可变参数机制。可以通过递归模板来处理不同类型的参数,避免了手动处理类型的麻烦。也就是支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包。
参数包有两种类型:
- 模板参数包,表示零或多个模板参数,使用class...或typename...关键字声明。
- 函数参数包,表示零个或多个函数参数,使用类型名后跟...表示。
template<class ...Arg> void Func(Arg... arg) {}
template<class ...Arg> void Func(Arg... arg) {}
template<class ...Arg> void Func(Arg... arg) {}
我们用省略号...来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class...或typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表。可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
sizeof... 运算符
sizeof...运算符来计算参数包中参数的个数。
template <class ...Arg>
void PrintArgNum(Arg&&... arg)
{cout << sizeof...(arg)<<"个参数包" << endl;
}int main()
{double a = 3.14;PrintArgNum();PrintArgNum(a);//一个参数PrintArgNum(1, string("241564132"));//两个参数return 0;
}
编译本质会结合引用折叠规则实例化出以下三个函数,在类型泛化基础上叠加了数量变化,让泛型编程更加灵活。
void Print();
void Print(double&& arg1);
void Print(int&& arg1, string&& arg2);
包扩展
对于一个参数包,我们除了计算它的参数个数,还可以对它进行包扩展。我们还要提供用于每个扩展元素的模式,扩展一个包就是将他分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放省略号(...)来触发扩展操作。
//参数包是0个时,直接匹配这个函数
void ShowList()
{cout<< endl;
}template<class T,class ...Arg>
void ShowList(T&& val,Arg&&... arg)
{cout << val << endl;//arg是N个参数的参数包,调用Printf,参数包的第一个传给val,剩下N-1个传给参数包,ShowList(arg...);
}
template <class ...Arg>
void Print(Arg&&... arg)
{cout << sizeof...(arg)<<"个参数包" << endl;ShowList(arg...);
}int main()
{double x = 3.14;Print();Print(11.1);//一个参数Print(1, string("bjkbhv"));//两个参数Print(12.55, string("9jjug7"), x);//三个参数return 0;
}
实际上是通过递归展开来实现的,当参数包为空时就会调用 void ShowList(),同时终止递归
递归时,T接受传来参数包的第一个参数类型,arg接受其余的参数类型,以此往复。