本文介紹 可變參數清單 版本的格式化輸入/輸出函數族群。這些函數接受 va_list 而非可變參數,適合建立自訂格式化包裝函數 (如日誌、除錯巨集)。
#include <stdarg.h>
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt); // 初始化
vprintf(fmt, ap); // 核心處理
va_end(ap); // 清理
}
| 函數 | 輸出目的地 | 原型 |
|---|---|---|
vprintf | stdout | int vprintf(const char *fmt, va_list ap); |
vfprintf | FILE *stream | int vfprintf(FILE *fp, const char *fmt, va_list ap); |
vsprintf | char *buffer | int vsprintf(char *buf, const char *fmt, va_list ap); |
vsprintf 安全性警告:不檢查緩衝區長度,易造成緩衝區溢位。C99 提供 vsnprintf(buf, size, fmt, ap) 限制長度,請優先使用。
| 函數 | 輸入來源 | 原型 |
|---|---|---|
vscanf | stdin | int vscanf(const char *fmt, va_list ap); |
vfscanf | FILE *stream | int vfscanf(FILE *fp, const char *fmt, va_list ap); |
vsscanf | char *string | int vsscanf(const char *buf, const char *fmt, va_list ap); |
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
FILE *logfp = NULL;
void log_init(const char *path) {
logfp = fopen(path, "a");
}
void log_msg(const char *fmt, ...) {
va_list ap;
char buf[256];
time_t now = time(NULL);
struct tm *t = localtime(&now);
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap); // 安全格式化
va_end(ap);
printf("[%02d:%02d:%02d] %s\n", t->tm_hour, t->tm_min, t->tm_sec, buf);
if (logfp) {
fprintf(logfp, "[%02d:%02d:%02d] %s\n", t->tm_hour, t->tm_min, t->tm_sec, buf);
fflush(logfp);
}
}
int main(void) {
log_init("app.log");
log_msg("System started, version %d.%d", 1, 0);
log_msg("User %s logged in", "admin");
fclose(logfp);
return 0;
}
char line[] = "42 3.14 hello";
int a; float b; char c[20];
va_list ap;
va_start(ap, c); // 僅示意,實際需包裝函數
vsscanf(line, "%d %f %s", ap); // 需自行建立 va_list
/* 正確寫法見下方包裝函數 */
正確包裝 vsscanf:
int scan_string(const char *src, const char *fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = vsscanf(src, fmt, ap);
va_end(ap);
return n;
}
/* 使用 */
scan_string(line, "%d %f %s", &a, &b, c);
重點提醒:
va_list 在函數間傳遞時屬「一次性」使用,若需多次掃描,必須用 va_copy (C99) 複製副本。v*printf 回傳輸出字元數 (不含結尾 null);v*scanf 回傳成功轉換項目數或 EOF。vsnprintf、vswprintf (寬字元) 等安全版本。