printf() 为什么能想传几个就传几个参数?因为它是变参函数(variadic function),参数个数不固定。自己写一个也不难,关键是搞懂 stdarg.h 里的四个家伙:va_list、va_start、va_arg、va_end。

什么是变参函数

变参函数指参数数量不固定的函数。它至少有一个固定参数,后面跟三个点 ...,表示接下来想传多少传多少:

return_type name(fixed_arg, ...);

四个必须认识的宏

访问变参要在 stdarg.h 里取工具,固定四步走:

1. va_list
声明一个参数列表变量,用来暂存到哪里去取参数的信息。
va_list list;
2. va_start()
初始化列表,第二个参数是最后一个固定参数,它标记变参从哪开始。
va_start(list, fixed_arg);
3. va_arg()
取出下一个变参,第二个参数写你要的类型。取几次就调几次,不能超数量。
va_arg(list, type);
4. va_end()
收尾清理,释放列表相关的资源。
va_end(list);

第一个例子:打印任意多个整数

第一个参数 n 告诉函数后面还有几个数,然后循环取出来打:

#include <stdio.h>
#include <stdarg.h>

/* 变参函数:打印 n 个整数 */
void print(int n, ...) {
    va_list args;
    va_start(args, n);

    for (int i = 0; i < n; i++)
        printf("%d ", va_arg(args, int));
    printf("\n");

    va_end(args);
}

int main() {
    /* 同一个函数,参数数量随便变 */
    print(3, 1, 2, 3);
    print(5, 1, 2, 3, 4, 5);
    return 0;
}

输出:

1 2 3
1 2 3 4 5

注意这里的妙处:固定参数 n 就是变参的数量。变参函数不知道你传了几个,所以数量必须由调用方通过某种方式告诉它,要么像这样传个数,要么用终止标记(比如读到 0 结束),要么靠格式串(printf 就是靠 % 个数)。

第二个例子:求任意多个数的和

#include <stdarg.h>
#include <stdio.h>

/* 变参函数:求 n 个数的和 */
int getSum(int n, ...) {
    int sum = 0;
    va_list list;

    va_start(list, n);
    for (int i = 0; i < n; i++)
        sum += va_arg(list, int);
    va_end(list);

    return sum;
}

int main() {
    printf("1 + 2 = %d\n", getSum(2, 1, 2));
    printf("3 + 4 + 5 = %d\n", getSum(3, 3, 4, 5));
    printf("6 + 7 + 8 + 9 = %d\n", getSum(4, 6, 7, 8, 9));
    return 0;
}

输出:

1 + 2 = 3
3 + 4 + 5 = 12
6 + 7 + 8 + 9 = 30

第三个例子:混合类型的变参

变参可以混类型,但取的时候类型要和你传的一致(va_arg 的类型写错是未定义行为)。用位置信息区分类型:

#include <stdio.h>
#include <stdarg.h>

/* 交替打印整数和浮点数 */
void print(int n, ...) {
    va_list args;
    va_start(args, n);

    for (int i = 0; i < n; i++) {
        if (i % 2 == 0) {
            printf("Integer: %d\n", va_arg(args, int));
        } else {
            printf("Float: %.2f\n", va_arg(args, double));
        }
    }
    va_end(args);
}

int main() {
    print(4, 10, 3.14, 20, 2.71);
    return 0;
}

输出:

Integer: 10
Float: 3.14
Integer: 20
Float: 2.71

注意事项

  • va_arg 取出的类型必须和传进去的一致,尤其 float 在变参里会提升成 double,得用 double 取。
  • 别取超过实际传入的数量,那是未定义行为。
  • 每个 va_start 必须配一个 va_end。

变参函数是 printf 家族和很多日志库的基础,自己实现一个简单的"迷你 printf"是巩固理解的好练习。