變參函數的實現

相對於固定參數的函數,變參函數的可用性無疑是更好的。我們最常用的變參函數包括scanfprintf。剛剛接觸到變參函數的時候,我覺得這太神奇了,它並不知道我要輸入什麼類型的數據,要輸入多少個數據,卻能完美地處理。其實,可變參數機制實現起來是相當容易的(在stdarg.h的基礎上),而且,它的作用並沒有想象中的那麼神奇。

         可變參數機制並不能獲取某次輸入的所有參數的個數,也不能自己確定每一個輸入參數的類型。嗯,沒錯,看上去printfscanf就能知道每次輸入的參數個數和每個參數的類型。其實,仔細想一想就會發現printfscanf沒這個本事,輸入的參數個數和每個參數的類型是使用者在format內容中,通過%模式等告訴編譯器的。光是這麼說可能不夠取信於人,以簡化的printf爲例,讓我們看一段K&R中的例程吧:

#include <stdarg.h>

/* minprintf: minimal printf with variable argument list */

void minprintf(char *fmt, ...)

{

    va_list ap; /* points to each unnamed arg in turn */

    char *p, *sval;

    int ival;

    double dval;

    va_start(ap, fmt); /* make ap point to 1st unnamed arg */

    for (p = fmt; *p; p++) {

       if (*p != '%') {

           putchar(*p);

           continue;

       }

       switch (*++p) {

           case 'd':

              ival = va_arg(ap, int);

              printf("%d", ival);

              break;

           case 'f':

              dval = va_arg(ap, double);

              printf("%f", dval);

              break;

           case 's':

              for (sval = va_arg(ap, char *); *sval; sval++)

                  putchar(*sval);

              break;

           default:

              putchar(*p);

              break;

       }

    }

    va_end(ap); /* clean up when done */

}

可以看出,printf處理可變參數的關鍵就在於它的參數char *fmt。注意程序中的for循環,指針p正是一次次地根據fmt中的提示(在這個簡化的例子中是%)經由switch分支來確定下一個參數的類型和有效參數的個數。

         所以說,可變參數不是萬能的,它只是一種很normal的機制,不過正是先驅們那化腐朽爲神奇的想象力,藉由這種normal的機制實現了神奇而又令人詬病的scanfprintf函數。一般而言,除了像printf一樣,在函數中實現一個特殊的format之外,一些可變參數中的參數類型都是一致的(比如說只可能都是int)函數,則會在之前的固定參數中用一個參數指出可變參數的個數或是類型。下面是一個C Primer Plus中的例程:

#include <stdarg.h>

double sum(int lim,...)

{

    va_list ap;                   // declare object to hold arguments

    double tot = 0;

    int i;

    va_start(ap, lim);            // initialize ap to argument list

    for (i = 0; i < lim; i++)

       tot += va_arg(ap, double); // access each item in argument list

    va_end(ap);                   // clean up

    return tot;

}

可以看出,函數sum中的固定參數lim指出了可變參數的個數。

         以上介紹了可變參數機制實現的兩種過程:自定義format和前置指示標誌。接下來就會詳細解釋在C語言中如何具體實現可變參數機制。從兩個例程可以看出,有四個宏是至關重要的:

va_list

va_start(va_list, lastpar)

va_arg(va_list, Type)

va_end(va_list)

va_list:一個char鏈表(實際上應該是一個連續的內存塊,像數組一樣),在使用時表現爲一個指向char類型的指針;

va_start:初始化va_list。通過最後的固定參數實現對可變參數初始位置的定位,併爲va_list分配內存,將可變參數複製該內存塊中,使va_list指向該內存塊的初始位置;

va_arg:通過移動指針va_list獲取由參數Type指定的變量並返回該變量。

va_end:釋放va_list擁有的內存塊所佔據的內存空間。

看,一切不就一清二楚了嗎?不過,還有如下幾個問題還需要特別注意一下:

1>     C標準規定實現可變參數機制的函數至少要有一個固定參數。從上面的討論可以看出,這無論是從語法上還是實現上都是必須的。

2>     隱式類型轉換不可用。比如說,va_arg指定了類型是double,若傳入的是int的變量10就會出錯。

3>     C語言的整型提升原則。也就是說,在va_arg中獲取floatdouble使用的Type都是double,而獲取charshortint使用的Type都是int

函數指針不可用,除非用typedef定義過。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章