变长参数列表

变长参数列表

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风格的边长参数列表

访问C风格的变长参数列表并不十分安全。从print int函数可以看出,这种方法存在以下的风险:

  • 不知道参数的数目。在printInts()中,必须信任调用者传递了与第一个参数指定的数目相等的参数。再debugOut()中,必须相信调用者在字符数组之后传递的参数数目与字符数组中的格式代码一致。
  • 不知道参数的类型。 va_arg()接收一种类型,用来解释当前的值。然而,可让va_arg()将这个值解释为任意类型,无法验证正确的类型。

推荐使用初始化列表

初始化列表在头文件中定义:利用初始化列表,可轻松地编写能接收可变数量参数的函数。 std::initializer_list是一个模板,要求在尖括号之间指定列表中的元素类型,这类似于制定vector中存储的对象类型。

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

你可能感兴趣的:(c++17/20/23,c++,c++,开发语言)