CC++ 中那些可变长参数

mac2024-04-08  40

C/C++提供了一些处理可变长参数/扩展参数包的宏、函数、模板,本文主要是记录下他们的使用方式

1. 使用<stdarg.h>中的宏

stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。可变参数的函数通在参数列表的末尾是使用省略号(,...)定义的。很多C语言库都是使用这种方式来处理输入参数列表的。

下面直接通过代码来演示如何使用,代码功能为统计N个参数的累加和:

#include <iostream> #include <cstdarg> int stdarg_counter(int count, ...) { int sum = 0; //这是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型 std::va_list args; //这个宏初始化args变量,它与 va_arg 和 va_end 宏是一起使用的。 //第二个参数count是最后一个传递给函数的已知的固定参数,即省略号之前的参数。 va_start(args, count); for (int i = 0; i < count; ++i) { //这个宏检索函数参数列表中类型为 type 的下一个参数 sum += va_arg(args, int); } //这个宏允许使用了 va_start 宏的带有可变参数的函数返回。 //如果在从函数返回之前没有调用 va_end,则结果为未定义。 va_end(args); return sum; } int main() { std::cout << "stdarg_counter:" << stdarg_counter(4, 1, 3, 5, 7) << std::endl; system("pause"); return 0; }

一般我们会组合使用多种参数类型,下面的代码功能为找键值对参数value对应的key值: 

int stdarg_find(int count, const char * target, ...) { int key = -1, find = -1; const char * value = nullptr; std::va_list args; va_start(args, target); for (int i = 0; i < count; ++i) { key = va_arg(args, int); value = va_arg(args, const char *); if (strcmp(target, value) == 0) { find = key; break; } } va_end(args); return find; } int main() { std::cout << "stdarg_find:" << stdarg_find(4, "gongjianbo", 12, "a", 1992, "gongjianbo", 5, "b") << std::endl; system("pause"); return 0; }

参考:https://zh.cppreference.com/w/cpp/utility/variadic/va_start 参考:https://www.runoob.com/cprogramming/c-standard-library-stdarg-h.html

2.使用__VA_ARGS__宏 

C99 引入了对参数个数可变的函数式宏的支持。在宏 “原型” 的末尾加上符号 … (就像在参数可变的函数定义中), 宏定义中的伪宏 __VA_ARGS__ 就会在调用是 替换成可变参数。如:

#define debug(...) printf(__VA_ARGS__)

这个宏好像在打印日志的场景里用的多点,网上的例子也多是类似的

#include <cstdio> #define debug(...) printf(__VA_ARGS__) int main() { debug("%s %d \n","gong jian bo",1992); system("pause"); return 0; }

 

参考:https://blog.csdn.net/bat67/article/details/77542165

因为C中提供的可变长参数机制缺少长度和类型检查,得靠使用者自己去处理,存在安全问题。而C++11也提供了两种方式来支持可变长参数:std::initializer_list参数列表、模板参数包。

3.使用C++11 std::initializer_list初始化器列表

std::initializer_list<T> 类型对象是一个访问 const T 类型对象数组的轻量代理对象,该类型定义在<initializer_list>中。与vector不同的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。还需要注意的是,std::initializer_list只能实例为一种类型,该类型对象只能用花括号 { } 初始化。

标准库中很多容器都使用了std::initializer_list来实现容器的列表初始化,使得初始化更方便:

(截图来自vector参考手册https://zh.cppreference.com/w/cpp/container/vector/vector)

下面也用他实现一个累加功能:

#include <iostream> #include <initializer_list> template<typename T> T list_counter(std::initializer_list<T> args) { T temp = T(); for (const T &item : args) { temp += item; } return temp; } int main() { std::cout << list_counter<double>({ 1, 2, 3.0, 4.5 }) << std::endl; // 1.5 system("pause"); return 0; }

std::initializer_list还支持迭代器,此处略

参考:https://zh.cppreference.com/w/cpp/utility/initializer_list

参考:https://www.jianshu.com/p/e5b781275ba9

4.使用C++11  Parameter packs(参数包)

模板形参包是接受零或更多模板实参(非类型、类型或模板)的模板形参。函数模板形参包是接受0或多个函数实参的函数形参。至少有一个形参包的模板被称作变参模板。

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号,形如:

template <class... T> void f(T... args);

(这个语法真的很挫,省略号位置都变了) 

变长参数包无法如同一般参数在类或函数中使用,因此典型的手法是以递归的方法取出可用参数:

#include <iostream> //用来结束递归 void my_cout() { std::cout << "end" << std::endl; } template<typename T,typename ... Types> void my_cout(T first,Types ... args) { //通过操作符 sizeof... 获取参数包中的参数个数 std::cout << sizeof...(args) <<" "<< first << std::endl; //递归取值 my_cout(args...); } int main() { my_cout(1, true, "gongjianbo", 1992, "last"); system("pause"); return 0; }

(参数包还有很多花样,我这里就不写了,详见参考文档。展开参数包稍显麻烦,而 C++17 的折叠表达式使得展开参数包变得容易,好吧,我也是为了学习折叠表达式才复习了下本文的内容) 

参考:https://zh.cppreference.com/w/cpp/language/parameter_pack

参考:https://www.cnblogs.com/qicosmos/p/4325949.html

折叠表达式:https://zh.cppreference.com/w/cpp/language/fold

折叠表达式:https://blog.csdn.net/ding_yingzi/article/details/79973809

最新回复(0)