变长实参

来自cppreference.com
< c‎ | language

变参数函数是能以不同数目参数调用的函数。

只有有原型函数声明可以有变长参数的。它必须通过出现在参数列表最后的 ... 形式的参数指定,并且跟随至少一个具名参数 (C23 前)。省略号形参与前驱的参数必须由 , 分隔。

// 有原型声明
int printx(const char* fmt, ...); // 此方法声明的函数
printx("hello world");     // 可能会以一个
printx("a=%d b=%d", a, b); // 或更多参数调用
 
int printz(...); // C23 起与 C++ 中 OK
// C23 前错误: ... 必须跟随至少一个具名参数
 
// int printy(..., const char* fmt); // 错误: ... 必须在末尾
// int printa(const char* fmt...);   // C 中错误:要求 ',' ;C++ 中 OK

函数调用中,每个属于变长参数列表一部分的参数会经历名为默认参数提升的隐式转换。

在函数体内使用变长参数时,这些参数的值必须用 <stdarg.h> 库工具访问:

在标头 <stdarg.h> 定义
令函数得以访问可变参数
(宏函数)
访问下一个函数可变参数
(宏函数)
创造函数可变参数的副本
(宏函数)
结束函数可变参数的行程
(宏函数)
保有 va_start 、 va_arg 、 va_end 及 va_copy 所需信息
(typedef)

注解

虽然旧式(无原型)函数声明允许后继的函数调用使用任意参数,但不允许它们为变长参数(C89 起)。这种函数的定义必须指定固定数目的参数,并且不能使用 stdarg.h 中的宏。

// 旧式声明, C++23 中移除
int printx(); // 此方式定义的函数
printx("hello world");     // 可以以一个
printx("a=%d b=%d", a, b); // 或更多参数调用
// 上述调用行为至少有一个是未定义的,取决于函数定义所接收的参数数

示例

#include <stdio.h>
#include <time.h>
#include <stdarg.h>
 
void tlog(const char* fmt,...)
{
    char msg[50];
    strftime(msg, sizeof msg, "%T", localtime(&(time_t){time(NULL)}));
    printf("[%s] ", msg);
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}
 
int main(void)
{
   tlog("logging %d %d %d...\n", 1, 2, 3);
}

输出:

[10:21:38] logging 1 2 3...

引用

  • C17 标准(ISO/IEC 9899:2018):
  • 6.7.6.3/9 Function declarators (including prototypes) (第 96 页)
  • 7.16 Variable arguments <stdarg.h> (第 197-199 页)
  • C11 标准(ISO/IEC 9899:2011):
  • 6.7.6.3/9 Function declarators (including prototypes) (第 133 页)
  • 7.16 Variable arguments <stdarg.h> (第 269-272 页)
  • C99 标准(ISO/IEC 9899:1999):
  • 6.7.5.3/9 Function declarators (including prototypes) (第 119 页)
  • 7.15 Variable arguments <stdarg.h> (第 249-252 页)
  • C89/C90 标准(ISO/IEC 9899:1990):
  • 3.5.4.3/5 Function declarators (including prototypes)
  • 4.8 VARIABLE ARGUMENTS <stdarg.h>

参阅