1. 程式人生 > >函式引數中的3個點表示什麼

函式引數中的3個點表示什麼

轉載於網友的一片文章,寫的很好!

         標準庫提供的一些引數的數目可以有變化的函式。例如我們很熟悉的printf,它需要有一個格式串,還應根據需要為它提供任意多個“其他引數”。這種函式被稱作“具有變長度引數表的函式”,或簡稱為“變引數函式”。我們寫程式中有時也可能需要定義這種函式。要定義這類函式,就必須使用標準標頭檔案<stdarg.h>,使用該檔案提供的一套機制,並需要按照規定的定義方式工作。本節介紹這個標頭檔案提供的有關功能,它們的意義和使用,並用例子說明這類函式的定義方法。
         一個變引數函式至少需要有一個普通引數,其普通引數可以具有任何型別。在函式定義中,這種函式的最後一個普通引數除了一般的用途之外,還有其他特殊用途。下面從一個例子開始說明有關的問題。
        假設我們想定義一個函式sum,它可以用任意多個整數型別的表示式作為引數進行呼叫,希望sum能求出這些引數的和。這時我們應該將sum定義為一個只有一個普通引數,並具有變長度引數表的函式,這個函式的頭部應該是(函式原型與此類似):
int sum(int n, ...)
        我們實際上要求在函式呼叫時,從第一個引數n得到被求和的表示式個數,從其餘引數得到被求和的表示式。在引數表最後連續寫三個圓點符號,說明這個函式具有可變數目的引數。凡引數表具有這種形式(最後寫三個圓點),就表示定義的是一個變引數函式。注意,這樣的三個圓點只能放在引數表最後,在所有普通引數之後。
        為了能在變引數函式裡取得並處理不定個數的“其他引數”,標頭檔案<stdarg.h>提供了一套機制。這裡提供了一個特殊型別va_list。在每個變引數函式的函式體裡必須定義一個va_list型別的區域性變數,它將成為訪問由三個圓點所代表的實際引數的媒介。下面假設函式sum裡所用的va_list型別的變數的名字是vap。在能夠用vap訪問實際引數之前,必須首先用“函式”va_start做這個變數初始化。函式va_start的型別特徵可以大致描述為:
va_start(va_list vap, 最後一個普通引數)
實際上va_start通常並不是函式,而是用巨集定義實現的一種功能。在函式sum裡對vap初始化的語句應當寫為:
va_start(vap, n);
在完成這個初始化之後,我們就可以通過另一個巨集va_arg訪問函式呼叫的各個實際引數了。巨集va_arg的型別特徵可以大致地描述為:
型別 va_arg(va_list vap, 型別名)
        在呼叫巨集va_arg時必須提供有關實參的實際型別,這一型別也將成為這個巨集呼叫的返回值型別。對va_arg的呼叫不僅返回了一個實際引數的值(“當前”實際引數的值),同時還完成了某種更新操作,使對這個巨集va_arg的下次呼叫能得到下一個實際引數。對於我們的例子,其中對巨集va_arg的一次呼叫應當寫為:
v = va_arg(vap, int);
這裡假定v是一個有定義的int型別變數。
        在變引數函式的定義裡,函式退出之前必須做一次結束動作。這個動作通過對區域性的va_list變數呼叫巨集va_end完成。這個巨集的型別特徵大致是:
void va_end(va_list vap);
下面是函式sum的完整定義,從中可以看到各有關部分的寫法:
int sum(int n, ...) {
      va_list vap;
       int i, s = 0;
       va_start(vap, n);
       for (i = 0; i < n; i++) s += va_arg(vap, int);
       va_end(vap);
       return s;
}
這裡首先定義了va_list變數vap,而後對它初始化。迴圈中通過va_arg取得順序的各個實參的值,並將它們加入總和。最後呼叫va_end結束。
下面是呼叫這個函式的幾個例子:
k = sum(3, 5+8, 7, 26*4);
m = sum(4, k, k*(k-15), 27, (k*k)/30);
         在編寫和使用具有可變數目引數的函式時,有幾個問題值得注意。首先,雖然在上面描述了標頭檔案所提供的幾個巨集的“型別特徵”,實際上這僅僅是為了說明問題。因為實際上我們沒辦法寫出來有關的型別,系統在預處理時進行巨集展開,編譯時即使發現錯誤,也無法提供關於這些巨集呼叫的錯誤資訊。所以,在使用這些巨集的時候必須特別注意型別的正確性,系統通常無法自動識別和處理其中的型別轉換問題。
        第二:呼叫va_arg將更新被操作的va_list變數(如在上例的vap),使下次呼叫可以得到下一個引數。在執行這個操作時,va_arg並不知道實際有幾個引數,也不知道引數的實際型別,它只是按給定的型別完成工作。因此,寫程式的人應在變引數函式的定義裡注意控制對實際引數的處理過程。上例通過引數n提供了引數個數的資訊,就是為了控制迴圈。標準庫函式printf根據格式串中的轉換描述的數目確定實際引數的個數。如果這方面資訊有誤,函式執行中就可能出現嚴重問題。編譯程式無法檢查這裡的資料一致性問題,需要寫程式的人自己負責。在前面章節裡,我們一直強調對printf等函式呼叫時,要注意格式串與其他引數個數之間一致性,其原因就在這裡。
        第三:編譯系統無法對變引數函式中由三個圓點代表的那些實際引數做型別檢查,因為函式的頭部沒有給出這些引數的型別資訊。因此編譯處理中既不會生成必要的型別轉換,也不會提供型別錯誤資訊。考慮標準庫函式printf,在呼叫這個函式時,不但實際引數個數可能變化,各引數的型別也可能不同,因此不可能有統一方式來描述它們的型別。對於這種引數,C語言的處理方式就是不做型別檢查,要求寫程式的人保證函式呼叫的正確性。
假設我們寫出下面的函式呼叫:
k = sum(6, 2.4, 4, 5.72, 6, 2);
        編譯程式不會發現這裡引數型別不對,需要做型別轉換,所有實參都將直接傳給函式。函式裡也會按照內部定義的方式把引數都當作整數使用。編譯程式也不會發現引數個數與6不符。這一呼叫的結果完全由編譯程式和執行環境決定,得到的結果肯定不會是正確的。