本文主要介绍了C语言的可变参数函数的基本使用方法。
一、引入
在讲解之前,让我们回到刚学习C语言的时候,看我们曾经写过的这样的一段C语言代码:
#include <stdio.h>
int main(void)
{
printf("Hello World!
");
printf("%s
", "Hello World!");
printf("%s %s
", "Hello", "World!");
return 0;
}
显然,这段代码会输出三行“Hello World!”。大多数人入门C语言,也是从这样的一个程序开始的。但是,请比较一下三行语句的printf函数,看看它们有什么区别。
仔细观察,我们发现,这三行语句中,printf函数分别有1、2、3个实参。而我们自己写过的大多数的函数,通常都只有固定的参数。在C语言不提供函数重载的情况下(实际上重载了也没用),如何实现这样的功能呢?
二、可变参数函数的声明
通常情况下,函数传参的过程是从右向左读取参数列表,并将其压入栈中的过程。在函数内可以从栈顶往下读取参数。如果我们将不同数量的参数同时压入栈中,再根据其数量从上往下依次读取,不就可以解决这个问题了吗?
在C语言中,我们可以在参数列表的最后且不唯一的一个参数,使用“...”来表示可变的参数。例如printf的函数原型:
int printf(const char *restrict format, ...);
对于函数而言,它是怎么获取参数的个数呢?在解答这个问题之前,我们再看看printf函数。printf函数是怎么知道我们有多少模式串提供给它呢?答案是printf的第一个参数。在format参数中,我们使用占位符来标记参数。我们使用了多少个占位符,意味着我们将会给printf函数提供多少参数。
就像printf函数一样,在我们自己书写函数的时候,可以要求用户将参数可变的参数数量的个数传入,例如下面的函数中的arg_cnt参数,可以被规定为可变参数的个数。
void foo(int arg_cnt, ...);
三、可变参数列表的使用
我们希望,在函数体中,能像操作一般的变量一样,操纵我们的参数。C语言给我们提供了这样的语法。
首先,需要在代码包含stdarg.h这个头文件。这个头文件提供了va_list
类型,用于表示可变参数的列表。然后提供了四个宏a_start
、va_arg
、va_end
、va_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_list
。va_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;
}
习题
- 自己实现一个printf函数。
- 了解C++的std::initializer_list。