c/c++中提供了语法和一些实现宏,用于编写具有可变数量的函数。这些函数通常看起来像printf()。尽管并非经常需要,但偶尔需要这个特性。例如,假设想编写一个快速调试的函数,如果设置了调试标记,这个函数向stderr输出字符串,但如果没有设置调试标记,就什么都不做。与printf()一样,这个函数应该能接收任意数目和任意类型的参数并输出字符串。这个函数的简单实现如下所示:
#include
#include
#include
using namespace std;
bool debug{false};
void debugOut(const char *str, ...)
{
va_list ap;
if (debug)
{
va_start(ap, str);
vfprintf(stderr, str, ap);
va_end(ap);
}
}
首先,注意debugOut()函数的原型包含一个具有类型和名称的参数str,之后是省略号(…),这代表任意数目和类型的参数。如果要访问这些参数,必须使用
中定义的宏。声明一个va_list类型的变量,并调用va_start()对其进行初始化。va_start()的第二个参数必须是参数列表中最右边的已命名变量。所有具有变长参数列表的函数都至少需要一个已命名的参数。debugOut()函数只是将列表传递给vfprintf()。vfprintf()的调用返回时,debugOut()调用va_end()来终止对变长参数列表的访问。在调用va_start()之后,必须总是调用va_end()以确保函数结束后,栈属于稳定的状态。函数debugOut()用法如下:
debug = true;
debugOut("int %d\n", 5);
debugOut("many ints: %d, %d, %d, %d, %d, %c\n", 1, 2, 3, 4, 5, 'W');
输出结果:
int 5
many ints: 1, 2, 3, 4, 5, W
如果需要访问实际参数,那么可以使用va_arg(),它接收va_list作为第一个参数,以及需要解析的参数的类型作为第二个参数。但是,如果不提供显式的方法,就无法知道参数列表的结尾是什么。例如,可以将第一个参数作为参数个数的计数。或者,当参数是一组指针时,可以要求最后一个指针是nullptr。方法很多,但对于程序员来说,所有方法都很麻烦。
下面的示例演示了这种技术,其中调用者在第一个以命名参数中指定了所提供参数的数目。函数接收任意数目的int参数,并将其输出。
void printInts(size_t num, ...)
{
va_list ap;
va_start(ap, num);
for (size_t i{0}; i < num; ++i)
{
int temp{va_arg(ap, int)};
cout << temp << " ";
}
va_end(ap);
cout << endl;
}
int main()
{
printInts(6,7,8,9,1,2,1); // 注意,第一个参数是,输出的后面整数的个数。本次为6个
return 0;
}
输出结果:
7 8 9 1 2 1
访问C风格的变长参数列表并不十分安全。从print int函数可以看出,这种方法存在以下的风险:
初始化列表在
import ;
import ;
using namespace std;
int makeSum(initializer_list<int> values)
{
int total{0};
for (int value : values)
{
total += value;
}
return total;
}
int main()
{
int a{makeSum({1, 2, 3})};
int b{makeSum({1, 2, 3, 4, 5, 6})};
// int c{makeSum({1, 2, 3.1})}; // 元素必须是列表中指定的类型
cout << "a = " << a << ", b = " << b << endl;
}
g++ -std=gnu++20 -fmodules-ts -x c++ test5.cc -xc++-system-header initializer_list
./a.out
a = 6, b = 21