在 C 語言中,printf()、scanf() 等標準庫函式都能接受不定數量的參數。這項強大功能透過 <stdarg.h> 標頭檔提供的巨集實現。本文將深入剖析可變參數機制,並示範如何編寫類似 average() 的自訂可變參數函式。
...,前一個參數通常為固定參數(如參數總數)。va_list 變數,呼叫 va_start(ap, last_fixed_arg) 綁定參數列表。va_arg(ap, type) 依序取出參數,必須知道每個參數的型別。va_end(ap) 釋放內部資源(雖然某些實作為空巨集,但標準要求必須呼叫)。#include <stdio.h>
#include <stdarg.h>
double average(int count, ...) {
va_list ap;
double sum = 0.0;
va_start(ap, count); // 1. 初始化
for (int i = 0; i < count; i++) {
sum += va_arg(ap, int); // 2. 取出 int 參數
}
va_end(ap); // 3. 清理
return (count > 0) ? sum / count : 0.0;
}
int main() {
printf("Average of 2, 3, 4, 5 = %.2f\n", average(4, 2, 3, 4, 5));
printf("Average of 5, 10, 15 = %.2f\n", average(3, 5, 10, 15));
return 0;
}
可變參數的真正威力在於配合格式字串解析。以下簡易版 my_printf 支援 %d、%s、%c:
#include <stdio.h>
#include <stdarg.h>
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt == '%') {
fmt++;
switch (*fmt) {
case 'd': printf("%d", va_arg(ap, int)); break;
case 's': printf("%s", va_arg(ap, char *)); break;
case 'c': printf("%c", (char)va_arg(ap, int)); break; // char 會提升為 int
default: putchar(*fmt);
}
} else {
putchar(*fmt);
}
fmt++;
}
va_end(ap);
}
int main() {
my_printf("Name: %s, Age: %d, Grade: %c\n", "Alice", 20, 'A');
return 0;
}
| 限制項目 | 說明 | 應對策略 |
|---|---|---|
| 無法自動得知參數數量 | 呼叫端必須透過格式字串、計數參數或哨兵值傳遞數量 | 慣例:第一個固定參數為 count,或使用格式字串解析 |
| 無法自動得知參數型別 | va_arg 需要程式設計師指定正確型別 | 嚴格遵循協定:%d 對應 int,%f 對應 double(float 會提升) |
| 預設引數提升 | char/short → int,float → double | va_arg(ap, int) 取 char,va_arg(ap, double) 取 float |
| 不支援非 POD 型別 | C++ 類別物件(含建構子/解構子)不可直接傳遞 | 改用 C++ 可變參數模板或 std::initializer_list |
va_arg(ap, long) 取 int 會導致堆疊讀取錯誤,後續參數全亂。建議:統一使用 int、double、char* 三種基本型別。va_end:雖然 GCC/Clang 多為空巨集,但 MSVC 可能需要清理堆疊框架,跨平台程式碼務必加上。va_list 上巢狀 va_start,需用 va_copy (C99) 複製列表。C11 引入 _Generic 可實現型別安全的多載;C++11 起推薦使用可變參數模板:
// C++17 折疊運算式
template <typename... Args>
double average(Args... args) {
return (args + ...) / sizeof...(args);
}
但在純 C 專案、嵌入式系統、標準庫實作中,<stdarg.h> 仍是不可替代的基石。
可變參數機制賦予 C 語言極大的 API 設計彈性。掌握 va_list 四部曲、理解預設引數提升規則、嚴守型別協定,就能編寫出如 printf 般靈活且高效的函式介面。記住:偉大的力量伴隨著偉大的責任——每一次 va_arg 都要對應正確的型別契約。