一、前言
我們通常編寫的函數都是參數固定的,多了少了都會有錯,但是有時候我們是不能確定預先需要多少個參數的,而變長參數函數恰恰就能解決我們的問題。在UNIX中,提供了變長參數函數的編寫方法,主要是通過va_list對象實現, 定義在文件'stdarg.h'中,變長參數函數的編寫有一個固定的模板,模板很簡單(見下代碼), 定義時, 變長參數列表通過省略號‘...’表示, 因此函數定義格式爲:
type 函數名(參數1, 參數2, 參數n, . . .);
變長參數函數模板:
view plaincopy to clipboardprint?
01.#include <stdarg.h>
02.int print (char * fmt, ...)
03.{
04. va_list args;
05.
06. /* do something here */
07.
08. va_start (args, fmt);
09.
10. /* do something here */
11.
12. va_end (args);
13.
14. /* do something here*/
15.}
#include <stdarg.h>
int print (char * fmt, ...)
{
va_list args;
/* do something here */
va_start (args, fmt);
/* do something here */
va_end (args);
/* do something here*/
}
變長參數函數實例:
view plaincopy to clipboardprint?
01.#include <stdarg.h>
02.int mysum(int i, ...){ // 參數列表中, 第一個參數指示累加數的個數
03. int r = 0, j = 0;
04. va_list pvar;
05. va_start(pvar, i);
06. for(j=0;j<i;j++)
07. {
08. r += va_arg(pvar, int);
09. }
10. va_end(pvar);
11. return(r);
12.}
13.int main()
14.{
15. printf("sum(1,4) = %d/n", mysum(1,4));
16. printf("sum(2,4,8) = %d/n", mysum(2,4,8));
17. return 0;
18.}
#include <stdarg.h>
int mysum(int i, ...){ // 參數列表中, 第一個參數指示累加數的個數
int r = 0, j = 0;
va_list pvar;
va_start(pvar, i);
for(j=0;j<i;j++)
{
r += va_arg(pvar, int);
}
va_end(pvar);
return(r);
}
int main()
{
printf("sum(1,4) = %d/n", mysum(1,4));
printf("sum(2,4,8) = %d/n", mysum(2,4,8));
return 0;
}
運行結果如下:
[root]# gcc -o mysum mysum.c
[root]# ./mysum
sum(1,4) = 4
sum(2,4,8) = 12
[root]#
在上面的運行結果中已經可以看到對於同樣一個函數mysum,我們兩次調用時傳入的參數不一樣,這在通常情況下編譯器會報錯,但現在由於使用了變長參數,所以可以正確的執行了。
二、宏的定義
前面說過va_list, va_start, va_end都是宏,網上查了下,關於這三個宏的定義,各編譯器不大一樣,下面是通過網絡得到的關於這三個宏的定義:
gcc中va_list的定義
#define char* va_list /* gcc中va_list等同char* */
gcc中三個宏的定義如下:
#define va_start(AP, LASTARG) ( /
AP = ((char *)& (LASTARG) + /
__va_rounded_size(LASTARG)))
通過對應第一節中的實例來分析一下這個宏,AP對應的是一個va_list對象,LASTARG自然對應的是mysum函數中的第一個參數i了,上面的意思就是首先取得第一個參數的首地址(通過(char *)& (LASTARG)這段實現的 ),然後將第一個參數首地址加上參數的大小( 通過__va_rounded_size(LASTARG)實現的),最終執行下來的結果是使AP指向mysum函數中的第二個參數。
#define va_arg(AP, TYPE) ( /
AP += __va_rounded_size(TYPE), /
*((TYPE *)(AP - __va_rounded_size(TYPE))))
其實這個宏的功能和上面的差不多,使AP指向當前參數的後面那個參數。因此,就可以通過一個循環調用這個宏,將所有參數讀出了
#define va_end(AP) /* 沒有定義,沒有操作 */
有的編譯器這樣定義:
#define va_end(AP) ((void *)0) /* 有定義,是空操作 */
三.不用宏的處理變長參數實踐:
從上面對宏的分析看中,瞭解到了宏是如何來實現變長函數的,原理如下:
1、首先獲得第一個參數的地址
2、通過第一個參數地址及其大小獲得下一個參數地址
3、按第2步的方式循環可以獲得第3、第4、第5……直到最後一個參數地址
這是一份從網上找到的不用宏實現的變長參數函數,它實現的方式正是按上面的原理進行:
view plaincopy to clipboardprint?
01.#include <stdio.h> /* 我沒包含 stdarg.h 或 vararg.h */
02.void print (char * fmt, ...)
03.{
04. char * arg; /* 變長參數的指針
05. 相當於 va_list arg */
06. int i; /* 接受int參數 */
07. double d; /* 接受double參數 */
08. char c; /* 接受char 參數*/
09. char *s; /* 接受字符串 */
10. printf ("%s", fmt); /* 打印第一個參數 fmt串 */
11. arg = (char *)&fmt + 4; /* 相當於 va_start(arg, fmt)
12. 這裏的 +4 實際上是
13. sizeof(char *) 因爲在IA32
14. 中,所以我寫了4 沒有考慮移植,
15.
16. 注意這裏加 4表示arg已經指向
17. 第二個參數 */
18. /* 打印第二個參數 */
19. i = *(int *)arg; /* 接受第二個參數,
20. 爲了直接了當,我硬性規定
21. print()函數的第二個
22. 參數是整數,請看
23. main()函數中的print()
24. 函數調用 */
25. printf ("%d", i); /* 打印地二個參數,是整數,
26. 所以用格式"%d" */
27. arg += sizeof(int); /* 指向下一個參數(第三個
28. 參數),爲什麼是加
29. sizeof(int),
30. 分析彙編碼你就明白了 */
31. /* 打印第三個參數 */
32. d = *(double *)arg; /* 以下的解釋同地二個參數類似,
33. 就不詳細解釋了 */
34. printf ("%f", d);
35. arg += sizeof(double);
36. /* 打印第四個參數 */
37. c = *(char *)arg;
38. printf ("%c", c);
39. arg += sizeof (char *);
40. /* 打印第五個參數 */
41. c = *(char *)arg;
42. printf ("%c", c);
43. arg += sizeof (char *);
44. /* 打印第六個參數 */
45. s = *(char **)arg;
46. printf ("%s", s);
47. arg += sizeof (char *);
48. arg = (void *)0; /* 使arg指針爲 (void)0,
49. 實際上就上使無效,否則arg
50. 依然指向第六個參數,危險。*/
51. /* 相當於 va_end(arg) */
52.}
53.
54.int main (void)
55.{
56. print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
57. return 0;
58.}
#include <stdio.h> /* 我沒包含 stdarg.h 或 vararg.h */
void print (char * fmt, ...)
{
char * arg; /* 變長參數的指針
相當於 va_list arg */
int i; /* 接受int參數 */
double d; /* 接受double參數 */
char c; /* 接受char 參數*/
char *s; /* 接受字符串 */
printf ("%s", fmt); /* 打印第一個參數 fmt串 */
arg = (char *)&fmt + 4; /* 相當於 va_start(arg, fmt)
這裏的 +4 實際上是
sizeof(char *) 因爲在IA32
中,所以我寫了4 沒有考慮移植,
注意這裏加 4表示arg已經指向
第二個參數 */
/* 打印第二個參數 */
i = *(int *)arg; /* 接受第二個參數,
爲了直接了當,我硬性規定
print()函數的第二個
參數是整數,請看
main()函數中的print()
函數調用 */
printf ("%d", i); /* 打印地二個參數,是整數,
所以用格式"%d" */
arg += sizeof(int); /* 指向下一個參數(第三個
參數),爲什麼是加
sizeof(int),
分析彙編碼你就明白了 */
/* 打印第三個參數 */
d = *(double *)arg; /* 以下的解釋同地二個參數類似,
就不詳細解釋了 */
printf ("%f", d);
arg += sizeof(double);
/* 打印第四個參數 */
c = *(char *)arg;
printf ("%c", c);
arg += sizeof (char *);
/* 打印第五個參數 */
c = *(char *)arg;
printf ("%c", c);
arg += sizeof (char *);
/* 打印第六個參數 */
s = *(char **)arg;
printf ("%s", s);
arg += sizeof (char *);
arg = (void *)0; /* 使arg指針爲 (void)0,
實際上就上使無效,否則arg
依然指向第六個參數,危險。*/
/* 相當於 va_end(arg) */
}
int main (void)
{
print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
return 0;
}
代碼有點長,其實很簡單,只是機械地對main()函數中的print()函數中的6個參數進行處理,依次打印上面的6個參數(注意作者沒有用格式化符號,帶格式話符號的處理函數我將在下面給出)
運行結果如下:
/************************
Hello
33.600000
aWorld
*************************/
與預想的完全一致。說明按上面的原理對變長參數的理解是正確的。
四、後記
爲什麼已經有宏實現了這麼一個變長參數函數的功能,我們還要去喫飽了撐的沒事幹,用函數來實現,何況在大多數情況下宏的運行效率要高於函數(一般來說宏產生較大的代碼,但是避免了函數調用的堆棧操作,所以速度會比較快),功能也沒別人強大,實現也不完善,這實在是喫力又不討好。不過畢竟這是自己去理解、去發現、去實現的東西,很簡單一個道理,就像做菜一樣,雖然自己的手藝不一定比得上大廚,但終究是自己做的,喫起來自然要香於現成的。
特別說明:文章中引用了很多別人的東西,引用地址如下:http://blog.sina.com.cn/s/blog_3e7df0e5010005ip.html~type=v5_one&label=rela_nextarticle在此對作者表示由衷的感謝
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/litingli/archive/2010/01/11/5176123.aspx