本文主要介绍了C语言的可变参数函数的基本使用方法。

[toc]

一、引入

在讲解之前,让我们回到刚学习C语言的时候,看我们曾经写过的这样的一段C语言代码:

#include <stdio.h>

int main(void)
{
    printf("Hello World!n");
    printf("%sn", "Hello World!");
    printf("%s %sn", "Hello", "World!");
    return 0;
}

显然,这段代码会输出三行“Hello World!”。大多数人入门C语言,也是从这样的一个程序开始的。但是,请比较一下三行语句的printf函数,看看它们有什么区别。

仔细观察,我们发现,这三行语句中,printf函数分别有1、2、3个实参。而我们自己写过的大多数的函数,通常都只有固定的参数。在C语言不提供函数重载的情况下(实际上重载了也没用),如何实现这样的功能呢?

二、可变参数函数的声明

通常情况下,函数传参的过程是从右向左读取参数列表,并将其压入栈中的过程。在函数内可以从栈顶往下读取参数。如果我们将不同数量的参数同时压入栈中,再根据其数量从上往下依次读取,不就可以解决这个问题了吗?

在C语言中,我们可以在参数列表的最后且不唯一的一个参数,使用“…”来表示可变的参数。例如scanf的函数原型:

int scanf(const char *restrict format, ...);

对于函数而言,它是怎么获取参数的个数呢?在解答这个问题之前,我们再看看printf函数。printf函数是怎么知道我们有多少模式串提供给它呢?答案是printf的第一个参数。在format参数中,我们使用占位符来标记参数。我们使用了多少个占位符,意味着我们将会给printf函数提供多少参数。

就像printf函数一样,在我们自己书写函数的时候,可以要求用户将参数可变的参数数量的个数传入,例如下面的函数中的arg_cnt参数,可以被规定为可变参数的个数。

void foo(int arg_cnt, ...);

三、可变参数列表的使用

我们希望,在函数体中,能像操作一般的变量一样,操纵我们的参数。C语言给我们提供了这样的语法。

首先,需要在代码包含stdarg.h这个头文件。这个头文件提供了va_list类型,用于表示可变参数的列表。然后提供了四个宏a_startva_argva_endva_copy用于操作它。

首先,我们设计这样的一个函数,并定义一个可变参数列表:

#include <stdarg.h>
#include <limits.h>    // 被INT_MIN宏所需要
int find_max_int(int arg_cnt, ...)  // 寻找最大的整数
{
    va_list ap;
    int max = INT_MIN;
    // ...
    return max;
}

va_start宏接受两个参数,原型为void va_start(va_list ap, parmN);,第一个参数是一个va_list;第二个参数为函数参数列表中,可变参数的前一个参数名,用于确定可变参数列表的位置。va_end只有一个参数,类型也为va_listva_arg有两个参数,第一个参数为va_list,第二个参数为要读取的变量的类型。而va_copy的作用,是将第二个va_list参数赋值给第一个va_list参数。

但是,C11标准文档指出:

The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , …). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.

即如果va_start的第二个参数是一个寄存器变量、函数、数组,或者是“不兼容应用默认参数提升后生成的类型”(机器翻译,建议读一下原文),则行为是未定义的。

根据上面的叙述,我们就可以完成这个函数了:

int find_max_int(int arg_cnt, ...)
{
    va_list ap;
    int max = INT_MIN;
    va_start(ap, arg_cnt);
    while (arg_cnt--)
    {
        int now = va_arg(ap, int); // va_list中,每个参数只可以被读取一次
        if (max < now)
        {
            max = now;
        }
    }
    va_end(ap);
    return max;
}

四、练习

  1. 自己实现一个printf函数。
  2. 了解C++的std::initializer_list。

By 方笛

发表评论