C++学习笔记----10、模块、头文件及各种主题(六)---- C风格可变长度参数列表
在传统代码中,可能会碰到使用C风格变量长度的参数列表。在新的代码中,应该避免使用这些,而要用类型安全的可变长度参数列表的可变参数函数模板,关于该模板,我们以后再讨论。
这样你就知道了C风格的可变长度参数列表,考虑<cstdio>中的C函数printf()。可以使用任意数量的参数来调用它:
import std;
using namespace std;
int main()
{
printf("int %d\n", 5);
printf("String %s and int %d\n", "hello", 5);
printf("Many ints: %d, %d, %d, %d, %d\n", 1, 2, 3, 4, 5);
}
C/C++提供了语法与书写带有可变参数数量的自己的函数的一些工具宏。这些函数通常看起来很像printf()。例如,假定你想写一个应急的debug函数来打印字符串到stderr,如果debug标志被设置,如果debug标志没有设置就什么也不做。与printf()类似,该函数应该能够打印任意数量的参数与任意类型的参数的字符串。简单的实现看起来如下:
import std;
#include <cstdarg>
#include <cstdio>
using namespace std;
bool debug{ false };
void debugOut(const char* str, ...)
{
if (debug) {
va_list ap;
va_start(ap, str);
vfprintf(stderr, str, ap);
va_end(ap);
}
}
代码使用了va_list(),va_start(),与va_end(),这些都是定义在<cstdarg>中的宏,因此需要显式的#include <cstdarg>,与import std;一样,不导出任何宏。类似地,stderr是一个定义在<cstdio>中的宏,需要显式的#include <cstdio>。
debugOut()的原型包含了一个类型化了的与命名的参数str,跟随的是...。代表了任意数量与类型的参数。要访问这些参数,必须声明一个va_list类型的变量并且调用va_start来初始化它。va_start()的第二个参数必须在参数列表的最右面。所有带有变长参数列表的函数要求至少一个命名参数。debugOut()函数只是将列表传递给vfprintf()(一个<cstdio>中的标准函数)。vfprintf()调用返回后,debugOut()调用va_end()来结束变量参数列表的访问。必须在调用va_start()之后调用va_end()来确保函数以可持续的状态的栈来结束。
可以用如下方式来使用该函数:
debug = true;
debugOut("int %d\n", 5);
debugOut("String %s and int %d\n", "hello", 5);
debugOut("Many ints: %d, %d, %d, %d, %d\n", 1, 2, 3, 4, 5);
1、访问参数
如果自己想要访问真实的参数,可以使用va_arg()来达成。它接受va_list作为第一个参数,以及要解释的参数类型。不幸的是,如果不提供显式的方式来这样做,是无法知道参数列表的结束的。例如,可以使第一个参数为参数数量。或者,如果有一系列的指针,可以要求最后一个指针为nullptr。有许多方法,但对于程序员来说都是负担。
下面的例子演示了调用者指定第一个命名参数提供了多少个参数的技巧。函数接受任意数量的int并且将它们打印出来。
void printInts(unsigned num, ...)
{
va_list ap;
va_start(ap, num);
for (unsigned i{ 0 }; i < num; ++i) {
int temp{ va_arg(ap, int) };
print("{} ", temp);
}
va_end(ap);
println("");
}
可以像下面这样调用printInts()。注意第一个参数指定了后面有多少个整数。
printInts(5, 5, 4, 3, 2, 1);
2、为什么不应使用C风格的变长参数列表
访问C风格的变长参数列表不太安全。有几个风险,在printInts()函数中就可以看出来。
- 不知道参数的数量。在printInts()中,必须相信调用者会传递正确的参数数量给到第一个参数。在debugOut()中,必须相信调用者传递同样数量的参数在str字符串之后,在字符串中要有替换域。
- 不知道参数的类型。va_arg()接受一个类型,它用于解释在当前方位的值。然而,可以告诉va_arg()来解释该值为任意类型。没有办法来验证正确的类型。
警告:避免使用C风格变长参数列表。推荐传递一个std::array或vector的值,使用初始化器列表或使用类型安全的可变长度参数列表的可变参数函数模板。