C/C++ 中那些可变长参数

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

1. 使用中的宏

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

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

#include 
#include 
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 

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

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

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

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

#include 
#include 

template
T list_counter(std::initializer_list args)
{
	T temp = T();
	for (const T &item : args) {
		temp += item;
	}
	return temp;
}

int main()
{
	std::cout << list_counter({ 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 
void f(T... args);

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

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

#include 

//用来结束递归
void my_cout()
{
	std::cout << "end" << std::endl;
}

template
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/C++ 中那些可变长参数_第1张图片

(参数包还有很多花样,我这里就不写了,详见参考文档。展开参数包稍显麻烦,而 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

你可能感兴趣的:(C++,没有结局的开始)