阅读背景:

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

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

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

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

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; }

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

. 函数本身并不知道传进来几个参数,比如我现在多传一个参数,或者少传一个参数,那么函数本身是检测不到这个问题的。这就可能会导致未定义的错误。 . 函数本身也不知道传进来的参数类型。以上面的例子,假如我把第二个参数1.0改成一个字符串,又如何?答案就是会得到未定义的错误,也就是不知道会发生什么。 . 对于可变长参数,我们只能用__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++ 使用可变参数模板 c++ 可变参数 方法一: C语言的: va_list1 #include <stdio.h



你的当前访问异常,请进行认证后继续阅读剩余内容。

分享到: