c++ 可变参数的三种实现方式

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

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 递归展开可变参数

  • 通过递归的方式展开
  1. 递归展开需要考虑爆栈的情况。 说到这里,ubuntu(linux) 默认栈大小8M(使用命令 ulimit -a查看), Win下,Visual Studio 默认栈大小1M(连接器->系统)。

  2. 需要两个函数:递归终止函数递归函数

  3. 一个例子,输出各个元素。函数参数为参数包的形式,使用递归展开

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 逗号表达式展开

使用逗号运算符是为了把几个表达式放在一起。

整个逗号表达式的值为系列中最后一个表达式的值。

从本质上讲,逗号的作用是将一系列运算按顺序执行。

image-20230112231912152

  • 逗号表达式拆包
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;
}

这个数组的目的纯粹是为了在数组构造的过程展开参数包。

猜你喜欢

转载自blog.csdn.net/sexyluna/article/details/128680795