Skip to content

C 可变参数

有时,创建可以接受可变数量参数而不是固定数量参数的函数会很有用。标准库中的例子包括 printf 和 scanf。这些函数称为 可变参数函数 (variadic functions)。

C 语言在 <stdarg.h> 头文件中提供了实现此类函数的机制。

可变参数函数的声明必须至少有一个具名参数 (named parameter),后跟省略号 (...) 作为最后一个参数。

return_type function_name(type fixed_param1, ...);

固定参数正常访问。省略号代表参数列表的可变部分。

<stdarg.h> 头文件提供了宏来处理可变参数:

  1. va_list: 一个类型,用于存储关于可变参数列表的信息。
  2. va_start(va_list ap, last_fixed_param): 初始化 va_list 变量 (ap)。last_fixed_param 必须是省略号前的最后一个具名参数的名称。必须在访问任何可变参数之前调用。
  3. va_arg(va_list ap, type): 从列表 (ap) 中检索下一个参数,并假设它具有指定的 type。它还会在 ap 内部将内部指针指向下一个参数。关键是,函数必须知道如何确定下一个参数的类型(例如,通过像 printf 那样的格式字符串,或通过固定参数传递的数量)。
  4. va_end(va_list ap): 清理 va_list。必须在函数返回之前,处理完所有参数后调用。
  5. va_copy(va_list dest, va_list src): (C99) 创建 va_list 状态的副本。如果需要多次遍历参数,这很有用。

让我们创建一个函数,计算可变数量的整型参数的平均值。我们将可变参数的数量作为第一个固定参数传递。

#include <stdio.h>
#include <stdarg.h> // 可变参数函数所需
// 计算后面 'count' 个整型参数的平均值
double average(int count, ...) { // 'count' 是最后一个固定参数
if (count <= 0) {
fprintf(stderr, "Error: Average requires at least one number.\n");
return 0.0; // 或以其他方式处理错误
}
va_list args; // 1. 声明一个 va_list 变量
double sum = 0.0;
// 2. 初始化 'args' 指向第一个可变参数
va_start(args, count);
// 3. 循环遍历并访问每个可变参数
for (int i = 0; i < count; i++) {
// 检索下一个参数,假设它是 int 类型
int current_arg = va_arg(args, int);
sum += current_arg;
}
// 4. 清理 va_list
va_end(args);
return sum / count;
}
int main(void) {
// 使用不同数量的参数调用 average
printf("Average of 2, 3, 4, 5 = %.2f\n", average(4, 2, 3, 4, 5));
printf("Average of 5, 10, 15 = %.2f\n", average(3, 5, 10, 15));
printf("Average of 100 = %.2f\n", average(1, 100));
printf("Average of nothing = %.2f\n", average(0)); // 处理错误情况
return 0;
}

输出:

Average of 2, 3, 4, 5 = 3.50
Average of 5, 10, 15 = 10.00
Average of 100 = 100.00
Error: Average requires at least one number.
Average of nothing = 0.00
  • 类型安全 (Type Safety): 可变参数函数会绕过对可变参数的常规类型检查。函数必须有一种可靠的方式来确定使用 va_arg 检索的每个参数的类型。传递错误类型的参数会导致未定义行为。
  • 参数提升 (Argument Promotion): 默认参数提升适用于可变参数(例如,float 会提升为 double,char/short 会提升为 int)。你必须在 va_arg 中使用提升后的类型(例如,使用 va_arg(ap, double) 来检索作为 float 传递的参数)。
  • 机制 (Mechanism): 函数需要一种机制来知道传递了多少参数以及它们的类型(例如,一个计数参数、一个格式字符串、一个哨兵值如 NULL)。

可变参数函数提供了灵活性,但需要仔细实现以确保正确性并避免运行时错误。