在C语言中,像 printf 和 scanf 这样的函数可以接受可变数量的参数,这得益于可变参数函数(Variadic Functions)机制。
通过 <stdarg.h> 头文件中提供的宏,我们可以定义自己的可变参数函数。
可变参数函数在日志系统、格式化输出、通用计算器等场景中非常有用。本章将详细讲解可变参数函数的实现方法及注意事项。
可变参数函数是指参数个数和类型在编译时不确定的函数。在函数原型中,用省略号 ... 表示可变参数部分。
int printf(const char *format, ...);
int my_sum(int count, ...); // 自定义可变参数函数
printf 的 format),用于确定后续参数的数量和类型。
<stdarg.h> 定义了以下类型和宏来处理可变参数:
va_list:用于存储可变参数信息的类型。va_start(ap, last_fixed):初始化 va_list 变量,last_fixed 是最后一个固定参数的名称。va_arg(ap, type):获取下一个参数,type 是参数的类型。va_end(ap):清理工作,结束可变参数的访问。va_copy(dest, src)(C99):复制 va_list 对象。#include <stdio.h>
#include <stdarg.h>
// 计算 n 个整数的和
int sum(int count, ...) {
int total = 0;
va_list args;
va_start(args, count); // 初始化,count是最后一个固定参数
for (int i = 0; i < count; i++) {
total += va_arg(args, int); // 每次获取一个int参数
}
va_end(args); // 清理
return total;
}
int main() {
printf("1+2+3 = %d\n", sum(3, 1, 2, 3)); // 6
printf("1+2+3+4+5 = %d\n", sum(5, 1, 2, 3, 4, 5)); // 15
return 0;
}
可变参数可以是不同类型,通常通过一个固定参数(如格式字符串)来指示后续参数的类型。
#include <stdio.h>
#include <stdarg.h>
// 自定义格式化输出(简化版)
void my_printf(const char *format, ...) {
va_list args;
va_start(args, format);
for (const char *p = format; *p != '\0'; p++) {
if (*p == '%') {
p++;
switch (*p) {
case 'd': {
int i = va_arg(args, int);
printf("%d", i);
break;
}
case 'f': {
double d = va_arg(args, double);
printf("%f", d);
break;
}
case 'c': {
// char 会被提升为 int
int c = va_arg(args, int);
putchar(c);
break;
}
case 's': {
char *s = va_arg(args, char*);
printf("%s", s);
break;
}
default:
putchar(*p);
}
} else {
putchar(*p);
}
}
va_end(args);
}
int main() {
my_printf("整数: %d, 浮点数: %f, 字符串: %s\n", 42, 3.14159, "Hello");
return 0;
}
char 和 short 提升为 int,float 提升为 double。
因此,va_arg(args, char) 是错误的,应使用 va_arg(args, int)。
除了通过固定参数指定数量,还可以使用哨兵值(如 NULL 或特殊值)来标记参数列表的结束。
#include <stdio.h>
#include <stdarg.h>
// 以 -1 作为结束标记的求和函数
int sum_until_minus_one(int first, ...) {
int total = first;
va_list args;
va_start(args, first);
int n;
while ((n = va_arg(args, int)) != -1) {
total += n;
}
va_end(args);
return total;
}
int main() {
printf("总和: %d\n", sum_until_minus_one(10, 20, 30, -1)); // 60
return 0;
}
float 自动提升为 double,char/short 提升为 int。#include <stdio.h>
#include <stdarg.h>
#include <time.h>
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
const char* level_strings[] = {
"DEBUG", "INFO", "WARN", "ERROR"
};
void log_message(LogLevel level, const char *file, int line, const char *format, ...) {
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_buf[20];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出时间和日志级别
fprintf(stderr, "[%s] [%s] [%s:%d] ", time_buf, level_strings[level], file, line);
// 处理可变参数
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, "\n");
}
// 宏简化调用,自动传入文件名和行号
#define LOG_DEBUG(...) log_message(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) log_message(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) log_message(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) log_message(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
int main() {
int x = 42;
double pi = 3.14159;
LOG_DEBUG("调试信息: x = %d", x);
LOG_INFO("程序启动成功");
LOG_WARN("警告: pi 值 = %.2f", pi);
LOG_ERROR("发生严重错误!");
return 0;
}
va_arg 指定的类型与实际参数类型不一致。va_arg(args, char) 获取 char 参数。va_arg 次数超过实际参数数量。
可变参数函数为C语言提供了极大的灵活性,但也带来了类型安全的风险。
在实际开发中,应谨慎使用,并确保调用者遵守约定的参数格式。掌握这一特性后,你可以编写出类似 printf 的强大函数。