首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言可變參數函數實現指南:stdarg.h 宏機制與多種求和範例

     編輯:關於C語言

核心機制:`` 四大宏

宏功能典型用法
va_list定義參數列表遍歷器(不透明類型)`va_list ap;`
va_start(ap, last_fixed)初始化 `ap` 指向第一個可變參數`va_start(ap, n2);` // `n2` 為最後一個固定參數
va_arg(ap, type)取得當前參數值並將 `ap` 移至下一個`int val = va_arg(ap, int);`
va_end(ap)清理 `ap` (釋放資源/重置)`va_end(ap);`
va_copy(dest, src)C99 新增:複製 `va_list` (避免二次遍歷失效)`va_copy(ap2, ap1);`

關鍵規則:可變參數**無自我描述能力**,調用者必須透過固定參數(如格式字串、計數器、哨兵值)告知函數參數個數與類型。

範例 1:哨兵值結束 (至少 3 參數,以 0 結尾)


#include 
#include 

// 固定前兩參數,後續可變,遇 0 結束
int sum_sentinel(int n1, int n2, ...) {
    va_list ap;
    int sum = n1 + n2; // 先累加固定參數

    va_start(ap, n2); // 從 n2 之後開始
    int val;
    while ((val = va_arg(ap, int)) != 0) { // 哨兵值 0
        sum += val;
    }
    va_end(ap);
    return sum;
}

int main() {
    printf("%d\n", sum_sentinel(2, 2, 2, 0));       // 6
    printf("%d\n", sum_sentinel(1,2,3,4,5,6,7,8,9,0)); // 45
    return 0;
}

範例 2:哨兵值結束 (至少 1 參數,以 0 結尾)


int sum_sentinel_v2(int n1, ...) {
    va_list ap;
    int sum = n1;
    va_start(ap, n1);
    int val;
    while ((val = va_arg(ap, int)) != 0) sum += val;
    va_end(ap);
    return sum;
}
// sum_sentinel_v2(2, 0) -> 2

範例 3:計數器指定個數 (浮點數求和)


double sum_count(int count, double first, ...) {
    va_list ap;
    double sum = first;
    va_start(ap, first);
    for (int i = 1; i < count; i++) { // 已取 first,剩 count-1 個
        sum += va_arg(ap, double);
    }
    va_end(ap);
    return sum;
}
// sum_count(3, 1.1, 2.2, 3.3) -> 6.6
// sum_count(2, 1.1, 2.2, 3.3) -> 3.3 (忽略多餘參數)

範例 4:格式字串驅動 (模擬 printf 簡易版)


double sum_format(const char *fmt, ...) {
    va_list ap;
    double sum = 0.0;
    va_start(ap, fmt);
    for (; *fmt; fmt++) {
        if (*fmt == '%') {
            fmt++; // 看下一個字元
            switch (*fmt) {
                case 'd': sum += va_arg(ap, int); break;    // int 提升自動處理
                case 'f': sum += va_arg(ap, double); break; // float 自動提升為 double
                default: break; // 忽略未知格式
            }
        }
    }
    va_end(ap);
    return sum;
}
// sum_format("%d,%f,%d,%f", 1, 1.1, 2, 2.2) -> 6.3

進階主題與陷阱

  1. 預設參數提升:可變參數傳遞時,`float` → `double`,`char`/`short` → `int`。`va_arg` **必須**用提升後類型(`double`、`int`),否則未定義行為。
  2. 無類型安全:編譯器不檢查 `va_arg` 類型與實際參數是否匹配。錯誤類型導致棧破壞/錯誤讀取。
  3. 二次遍歷需 `va_copy`:`va_list` 可能為單次遍歷迭代器(如陣列指標或結構體)。需二次遍歷時必須 `va_copy` 備份。
  4. 結構體傳值:C 允許傳結構體值,但 `va_arg` 需完整類型名,且 ABI 可能涉寄存器/棧混合傳遞,極易出錯。**建議傳指標**。
  5. 哨兵值選擇:整數用 `0` 或 `-1`;指標用 `NULL`;浮點數難用哨兵(無通用無效值),**首選計數器或格式字串**。

實際應用場景

  • 日誌函數:`log_printf(level, fmt, ...)`
  • 字串格式化:`vsnprintf` 封裝。
  • 容器初始化:`list_create(3, "a", "b", "c", NULL)`。
  • 委派/回調:`execute(callback, arg1, arg2, ...)`。

總結

可變參數提供極大靈活性,但犧牲類型安全與效能。現代 C 開發中,固定參數 + 結構體/陣列/指標 往往是更佳選擇。若必須使用,嚴格遵循「計數器/格式字串驅動」原則,並善用 `va_copy` 處理複雜邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved