一. 什么是可变参数函数
如果你接触 C 语言,那你一定不会对 printf 函数感到陌生,比如使用 printf("%d + %d = %d\n", a, b, a + b) 打印变量 a 和 b 相加的结果,也可以直接使用 printf("Hello, Meng bie ting\n") 往屏幕上输出字符串,printf 函数就是一个可变参数函数,第一个参数可以看做是一个格式化说明,后面再加上参数个数和类型都不定的变量。所以我们可以说:可变参数函数就是一种可以接收非固定个数参数的函数,比如我们要写一个函数,这个函数功能是实现对任意个数的参数求和并返回,就需要用到这种可变参数的函数了。
二. 可变参数函数的实现方式
1. C 语言中的实现方式
在 C 语言时代,我们使用省略号形参(...)来代表可变参数列表,省略号形参一般只能处理简单的类型,如 int,char。因为在 C++ 中包含了大量复杂的类类型,所以在 C++ 中实现可变参数函数我们并不使用这种方式。
省略号形式的参数,虽然参数数量不定但是函数的所有参数都是存储在线性连续的栈空间的,注意可变参数函数必须至少要有一个普通参数,通过这个普通参数,我们就可以寻址获得后续可变参数的类型以及值,使用前要包含头文件 。
例子:一个计算可变个数参数所有参数和的函数
double Sum(int num, ...) // 必须有一个普通参数
{
va_list valist; // 创建一个va_list类型的变量
double sum = 0;
va_start(valist, num); // 让可变参数列表知道第一个参数的位置,方便寻址后续的变参
for (int i = 0; i < num; ++i)
{
sum += va_arg(valist, double); //告诉 valist 后面的变参分别是什么类型的
}
va_end(valist); // 释放valist
return sum;
}
注意事项
(1)如果有多个普通函数,va_start 要绑定的是 ... 左边的那个参数。
(2)... 只能放在参数列表最右边,不能放在普通变量左边。
(3)... 之前的逗号可以省略,即上面的函数写成,double Sum(int num...)。
(4)一般这些可变参数类型是数值型或者字符串型,如果是复杂类类型一般都不能处理所以 ... 可变参的应用场景比较受限。
2. C++ 中的实现方式
C++11 提供了一种标准库模板类类型 initializer_list 让我们处理实参类型相同但是个数可变的函数。这一次我们使用 initializer_list 来打印一个可变个数参数值的函数。在编写代码前,我们需要先熟悉 initializer_list 的常用操作。
(1)定义initializer_list
initializer_list<int> mylist1;
initializer_list<int> mylist2 = {1,2,3,4,5};
注意:initializer_list 中的元素是常量值,一旦定义了就不能被改变。
(2)initializer_list的成员方法
initializer_list 中的成员方法很少,有 begin,end,size,使用如下:
initializer_list<int> mylist = {1,3,5,6,72,2,123};
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
cout << *it << " ";
}
// 上面的遍历方式可以改为如下,因为范围 for 的原理就是使用迭代器。
for (const auto &elem : mylist)
{
cout << elem << " ";
}
(3)initializer_list的拷贝和赋值
拷贝和赋值一个 initializer_list 对象不会拷贝列表中的元素,原来对象和拷贝赋值出来的对象共享表中的元素(类似于浅拷贝)。举例如下:
initializer_list<int> mylist1 = {1,2,3,4};
initializer_list<int> mylist2(mylist1);
initializer_list<int> mylist3;
mylist3 = mylist1;
使用拷贝构造和等号赋值后,虽然 mylist1,mylist2 和 mylist3 对象本身的地址不相同,但是它们里面的值所在的地址是相同的,即 1,2,3,4 内容本身只有一份,这个特性跟常见的容器特性不太相同。
例子:一个打印可变个数参数所有值的函数
#include <initializer_list>
#include <string>
using namespace std;
void Log(initializer_list<string> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
cout << *it << " ";
}
}
int main()
{
Log({ "Hello", "World", "C++" });
return 0;
}