1. 程式人生 > 實用技巧 >c(++) 變長引數之整形(非字串型別類似)

c(++) 變長引數之整形(非字串型別類似)

0、序言

  變長引數,接觸的第一個可變長引數函式是 printf , 然後是 scanf 。他們的原型如下:

printf:

_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL printf(
    _In_z_ _Printf_format_string_ char const* const _Format,
    ...)

scanf

_CRT_STDIO_INLINE int __CRTDECL scanf(
    _In_z_ _Scanf_format_string_ char const* const _Format,
    ...)

  本文演示的是資料型別是 int .

1、使用

  A、標頭檔案

// 使用va_start需要的標頭檔案
#include <stdarg.h>

  B、使用必須使用的4個巨集,原型就不貼出來了,大家更關心的是怎麼使用。:

  va_list :儲存可變長引數

  va_start:構建一個引數集合

  va_arg:獲取集合中的引數

  va_end:釋放集合

  C、和普通引數使用相似,第一個引數必須要指定為型別,後面 寫三個 點。例如:

vodi add(const int param, ...);

 

2、一個完整的例子  

  演示環境: VS2015 up3

  這裡,定義了一個函式,函式用來求和,引數型別為整形,引數為可變長引數

 1 #include <iostream>
 2 
 3 // 使用va_start需要的標頭檔案
 4 #include <stdarg.h>
 5 
 6 // 不定長引數求和
 7 void va_sum(const int first_param, ...)
 8 {
 9 
10     va_list ap;
11 
12     va_start(ap, first_param);
13 
14     // 儲存求和的結果, 演示不定長引數用法,暫時不考慮越界
15     int sum = 0
; 16 17 // 中間每一項 18 int tmp = 0; 19 20 // -1 表示 集合的結束 21 for (int i = 0; -1 != tmp ; i++) 22 { 23 // 顯示第i個引數 24 std::cout << "i=" << i << ", tmp=" << tmp << "\n"; 25 // 儲存結果 26 sum += tmp; 27 // 找到下一個引數 28 tmp = va_arg(ap, int); 29 30 } 31 32 // 釋放空間 33 va_end(ap); 34 35 // 輸出結果 36 std::cout << "sum = " << sum << "\n"; 37 } 38 39 // 入口函式 40 int main(int argc, char *argv[]) 41 { 42 // -1 表示可變長引數的結束 43 va_sum(1, 2, 3, 4, -1); 44 45 system("pause"); 46 return 0; 47 }

  這段程式碼,大家先看下, 預測下 sum 的輸出結果。 下面將有正確答案。

  我第一次作答的答案: 函式 va_sum 用來求和的, main函式傳入了引數: 1. 2. 3. 4. -1. 所以, va_sum中的sum=1 + 2 + 3 + 4 = 10。 sum將輸出 10。

  正確答案是:

   sum 怎麼不是10 ? 怎麼i= 0,輸出的不是第一個引數1, 而是 2?

  踩坑:檢查發現, sum 少加了 引數 1。 閱讀上面的程式碼,不難發現, 當 第一次 呼叫 va_arg 巨集時, 此時得到的是集合的第1個引數,而不是呼叫傳入的引數1. 1 是第一個引數。

   具體,咱們看下 va_start, va_end, va_arg的巨集定義(來自Vs2015up3中vadefs.h檔案的定義)

1    #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
2     #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
3     #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

  明白了。

  怎麼改實現求和? sum 加上 第一個引數 :first_param 即可。va_sum 函式的for迴圈前增加一行程式碼:

// 新增加
    sum += first_param;

  va_sum函式完整定義:

 1 // 不定長引數求和
 2 void va_sum(const int first_param, ...)
 3 {
 4 
 5     va_list ap;
 6 
 7     va_start(ap, first_param);
 8 
 9     // 儲存求和的結果, 演示不定長引數用法,暫時不考慮越界
10     int sum = 0;
11 
12     // 中間每一項
13     int tmp = 0;
14 
15     // 新增加
16     sum += first_param;
17 
18     // -1 表示 集合的結束
19     for (int i = 0; -1 != tmp ; i++)
20     {
21         // 顯示第i個引數
22         std::cout << "i=" << i << ", tmp=" << tmp << "\n";
23         // 儲存結果
24         sum += tmp;
25         // 找到下一個引數
26         tmp = va_arg(ap, int);
27         
28     }
29 
30     // 釋放空間
31     va_end(ap);
32     
33     // 輸出結果
34     std::cout << "sum = " << sum << "\n";
35 }

  輸出結果:

3、總結

  A、第一次呼叫 va_arg 指向的是集合的第一個元素,而不是呼叫時傳入的第一個引數。

  B、va_list 儲存的時 函式引數 三個點【...】的引數,呼叫時傳入的第一個引數儲存在函式的第一個引數中。

  C、va_start 與 va_end 需要配對使用。

  D、需要指定結束符。 這裡 -1 為結束符。