变长实参

来自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 中的宏。

// 旧式声明,C23 中移除
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>

参阅