C语言中,函数可变参数可以接受不定数量的参数。常见的例子是标准库中的 printf() 和 scanf() 函数,它们能够处理不同数量和类型的输入参数。实现可变参数的功能需要使用标准库中的 <stdarg.h> 头文件。尽管这种机制增加了函数的灵活性,但使用时需要小心,确保正确处理参数类型和数量。本文主要介绍C语言中函数的可变参数。

1、可变参数

可变参数是指函数带有可变数量的参数,而不是预定义数量的参数。函数需要固定数量的强制参数(mandatory argument),后面是数量可变的可选参数(optional argument)。C语言中最常用的可变参数函数例子是 int printf(const char *format, ...)

参数列表的格式是强制性参数在前,后面跟着一个逗号和省略号(...),这个省略号代表可选参数。

例如,

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

// 可变参数函数:求多个整数的和
int add(int count, ...) {
    va_list args;  // 定义 va_list 变量
    va_start(args, count);  // 初始化 va_list,将参数指针指向已知的参数 count

    int total = 0;
    
    // 循环获取所有可变参数
    for (int i = 0; i < count; i++) {
        int num = va_arg(args, int);  // 获取下一个参数,类型为 int
        total += num;  // 计算总和
    }

    va_end(args);  // 结束可变参数的处理
    return total;  // 返回总和
}

int main() {
    // 调用 add 函数,传入3个参数
    int result = add(3, 10, 20, 30);
    printf("结果: %d\n", result);

    return 0;
}

注意:函数 add() 最后一个参数写成省略号,即三个点号(...),省略号之前的那个参数是 int n,代表了要传递的可变参数的总数。为了使用这个功能,需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。

#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

1)定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。

在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。

2)使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。

3)使用 va_arg 宏和 va_list 变量来访问参数列表中的每个元素。

4)使用宏 va_end 来清理va_list 变量的内存。

5)宏va_start通过该宏定义可以获取到可变参数表的首地址,并将该地址赋给指针ap

6)宏va_arg通过该宏定义可以获取当前ap所指向的可变参数,并将指针ap指向下一个可变参数。注意,该宏的第二个参数为类型。

7)宏va_end通过该宏定义可以结束可变参数的获取。

例如,

#include <stdio.h>
#include <stdarg.h>
void myprintf(const char *format, ...)
{
    va_list ap;
    char c;
    va_start(ap, format);
    while (c = *format++) {
        switch(c) {
            case 'c': {
                char ch = va_arg(ap, int);
                putchar(ch);
                break;
            }
            case 's': {
                char *p = va_arg(ap, char *);
                fputs(p, stdout);
                break;
            }
            default:
                putchar(c);
        }
    }
    va_end(ap);
}
int main(void)
{
    myprintf("c\ts\n", '1', "hello");
    return 0;
}

2、可变参数函数的基本结构

定义可变参数函数时,函数必须至少有一个已知的参数(作为标志或基准),从该参数之后可以传递任意数量和类型的参数。

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

void function_name(known_type arg1, ...) {
    va_list args;  // 定义一个 va_list 类型的变量来存储参数列表
    va_start(args, arg1);  // 初始化 va_list,并从已知参数 arg1 开始获取可变参数

    // 使用 va_arg 获取参数
    type = va_arg(args, type);

    va_end(args);  // 结束可变参数处理
}

3、处理不同类型的可变参数

可变参数不仅可以处理相同类型的参数,也可以处理不同类型的参数。为此,需要明确每个参数的类型,以便在调用 va_arg 时传递正确的类型。

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

void print_values(const char* format, ...) {
    va_list args;
    va_start(args, format);

    const char* p = format;
    while (*p) {
        switch (*p) {
            case 'i': {  // 打印整数
                int val = va_arg(args, int);
                printf("%d ", val);
                break;
            }
            case 'c': {  // 打印字符
                char val = (char)va_arg(args, int);  // 注意:char 在 va_arg 中会被提升为 int
                printf("%c ", val);
                break;
            }
            case 'f': {  // 打印浮点数
                double val = va_arg(args, double);
                printf("%f ", val);
                break;
            }
        }
        p++;
    }
    
    va_end(args);
    printf("\n");
}

int main() {
    print_values("icf", 42, 'A', 3.14);  // 输出: 42 A 3.140000
    return 0;
}

注意:

可变参数函数不进行类型检查,因此调用者必须确保提供的参数类型和数量与函数内部的处理相匹配。否则可能会导致未定义行为或崩溃。在可变参数列表中,charshort 类型会被提升为 intfloat 会被提升为 double。因此在处理这些类型时,需要特别小心。由于可变参数没有确定的数量,通常需要通过某种方式告知函数何时停止读取参数,例如传递一个参数表示参数个数,或者使用特殊标志(如 NULL)标识参数结束。

推荐文档