c++ 可变参数
方法一: C语言的: va_list1
#include <stdio.h>
#include <stdarg.h>
int add_nums(int count, ...)
{
int result = 0;
va_list args;
va_start(args, count); // C23 起能省略 count
for (int i = 0; i < count; ++i) {
result += va_arg(args, int);
}
va_end(args);
return result;
}
int main(void)
{
printf("%d\n", add_nums(4, 25, 25, 50, 50));
}
- \1. 函数本身并不知道传进来几个参数,比如我现在多传一个参数,或者少传一个参数,那么函数本身是检测不到这个问题的。这就可能会导致未定义的错误。
- \2. 函数本身也不知道传进来的参数类型。以上面的例子,假如我把第二个参数1.0改成一个字符串,又如何?答案就是会得到未定义的错误,也就是不知道会发生什么。
- \3. 对于可变长参数,我们只能用__cdecl调用约定,因为只有调用者才知道传进来几个参数,那么也只有调用者才能维持栈平衡。如果是__stdcall,那么函数需要负责栈平衡,可是函数本身根本不知道有几个参数,函数调用结束后,根本不知道需要将几个参数pop out。(注:某些编译器如VS,如果用户写了个__stdcall的可变长参数函数,VS会自动转换成__cdecl的,当然这是编译器干的事情)
方法二:C++语言的: initializer_list
- initializer_list API
void my_print1(int a, initializer_list<string> il)
{
cout << a << endl;
for (auto &i : il)
cout << i << " ";
cout << endl;
}
//函数调用
int main()
{
my_print1(2, {"lxb", "zhj"});
return 0;
}
方法三:c++ 使用可变参数模板
一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包function parameterpacket),表示零个或多个函数参数。
C++11新特性: 可变参数模版
3.1 可变参数个数
- 可变参数的个数:
template<class... Types>
struct count
{
static const std::size_t value = sizeof...(Types);
};
3.2 递归展开可变参数
- 通过递归的方式展开
-
递归展开需要考虑爆栈的情况。 说到这里,ubuntu(linux) 默认栈大小8M(使用命令 ulimit -a查看), Win下,Visual Studio 默认栈大小1M(连接器->系统)。
-
需要两个函数:递归终止函数 和 递归函数
-
一个例子,输出各个元素。函数参数为参数包的形式,使用递归展开
double Sum2() // 边界条件
{
return 0;
}
template<typename T1, typename... T2>
double Sum2(T1 p, T2... arg)
{
double ret = p + Sum2(arg...);
return ret;
}
int main() {
cout << Sum2(2,2,2,2);
}
- 递归终止改为一个参数
template<class T>
void print(T &t) {cout << t <<'\n';}
template<class T, class... Args>
void print(T &t, Args&... rest) {
cout << t << ' ';
print(rest...); // 打印剩余参数,注意省略号必须有
}
3.3 逗号表达式展开
使用逗号运算符是为了把几个表达式放在一起。
整个逗号表达式的值为系列中最后一个表达式的值。
从本质上讲,逗号的作用是将一系列运算按顺序执行。
- 逗号表达式拆包
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
- 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值进行返回。
- 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
- 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。
- 这里我们把逗号表达式的最后一个值设为0,此时我参数包里有几个参数,那么就有几个0,也就代表有几个值
当然,这里其实不用逗号表达式也可以,直接给PrintArg函数带上返回值即可完成逗号表达式的功能:
template <class T>
int PrintArg(const T& t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
//列表初始化
int arr[] = { PrintArg(args)... };
cout << endl;
}
此时可以传入多种类型的参数了,但是不能不传参数,因为数组的大小不能为0,为了支持不传参数,我们需要单独写个无参的ShowList函数,就像无参版的终止函数那样:
//支持无参调用
void ShowList()
{
cout << endl;
}
template <class T>
int PrintArg(const T& t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
//列表初始化
int arr[] = { PrintArg(args)... };
cout << endl;
}
这个数组的目的纯粹是为了在数组构造的过程展开参数包。