可變參數函數允許參數個數、類型在調用時確定。標準庫 printf、scanf、execl 皆基於此機制。C 語言通過 <stdarg.h> 提供四大工具:
| 宏/類型 | 作用 | 原型 |
|---|---|---|
va_list | 參數列表游標(不透明類型) | 實現定義:`typedef char *va_list` 或結構體 |
va_start | 初始化游標指向第一個可變參數 | void va_start(va_list ap, last_fixed_param); |
va_arg | 按指定類型取出下一個參數,推進游標 | type va_arg(va_list ap, type); |
va_end | 清理游標(某些架構需棧恢復) | void va_end(va_list ap); |
va_copy | C99:複製游標(多次遍歷必需) | void va_copy(va_list dest, va_list src); |
#include <stdarg.h>
返回類型 func(固定參數, ...) {
va_list ap;
va_start(ap, last_fixed); // 1. 初始化
while (還有參數) {
type val = va_arg(ap, type); // 2. 逐個獲取
// 處理 val
}
va_end(ap); // 3. 清理
return 結果;
}
printf:格式字符串指示類型(`%d`→`int`,`%f`→`double`)。execl:以 `NULL` 結束的 `char*` 列表。va_copy:`va_arg` 消耗游標,二次遍歷前必須 `va_copy` 備份(C99)。void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt == '%') {
switch (*++fmt) {
case 'd': {
int val = va_arg(ap, int);
char buf[16];
int len = sprintf(buf, "%d", val);
fwrite(buf, 1, len, stdout);
break;
}
case 's': {
const char *s = va_arg(ap, const char *);
fwrite(s, 1, strlen(s), stdout);
break;
}
case 'f': {
double val = va_arg(ap, double); // float 提升為 double
char buf[32];
int len = sprintf(buf, "%.6f", val);
fwrite(buf, 1, len, stdout);
break;
}
default: putchar(*fmt);
}
} else {
putchar(*fmt);
}
++fmt;
}
va_end(ap);
}
double average(int count, ...) {
va_list ap;
va_start(ap, count);
double sum = 0;
for (int i = 0; i < count; ++i)
sum += va_arg(ap, double); // 實參傳 double 或 int(提升為 double)
va_end(ap);
return count ? sum / count : 0;
}
// 調用
printf("%.2f\n", average(3, 1.0, 2.0, 3.0)); // 2.00
printf("%.2f\n", average(5, 10, 20, 30, 40, 50)); // 30.00 (int 提升為 double 後轉換)
char *str_concat(const char *first, ...) {
va_list ap, ap2;
va_start(ap, first);
va_copy(ap2, ap); // 備份用於二次遍歷
// 第一遍:計算總長度
size_t total = strlen(first);
const char *p;
while ((p = va_arg(ap, const char *)) != NULL)
total += strlen(p);
va_end(ap);
// 分配內存
char *result = malloc(total + 1);
if (!result) { va_end(ap2); return NULL; }
// 第二遍:拷貝
char *dst = stpcpy(result, first);
while ((p = va_arg(ap2, const char *)) != NULL)
dst = stpcpy(dst, p);
va_end(ap2);
return result; // 調用者負責 free
}
// 調用
char *s = str_concat("Hello", " ", "World", "!", NULL);
puts(s); // Hello World!
free(s);
結構體作可變參數傳遞時,按值壓棧,可能涉及對齊填充。建議傳指針:
void draw_shapes(int count, ...) {
va_list ap;
va_start(ap, count);
for (int i = 0; i < count; ++i) {
const struct Shape *s = va_arg(ap, const struct Shape *);
// 繪製 s
}
va_end(ap);
}
va_start/arg/end/copy 四件套,注意默認提升(float→double, char/short→int)。__builtin_va_arg_pack() (GCC/Clang) 輔助。