C++中有函數重載這種方法,以供我們調用時要可以不確定實參的個數,其實 C 語言也可以,而且更高明!
我們在stdio.h 中可以看到 printf() 函數的原型:
int printf(char * format,...)
事實上,我們如果要寫這樣的函數也可以類似的寫,那麼在定義函數時用上這個符號“ ... ” ,它叫占位符,喊它 “ 三個點 ” 也可以,只要你願意!那麼我可以這樣定義我的函數:
fun(int a,...) { }
這是個空函數,它是什麼都不做的,光這樣寫還不行的,具體應該怎樣定義呢?
且聽我介紹3 個知識:
1、 va_list
2、 va_arg()
3、 va_start()
程序在執行時,會將函數存儲到內存中去。現在深入的講一點點,存儲函數時,參數傳遞的過程是怎樣實現的呢?所謂的形式參數(局部變量)實質上又是什麼呢?把這些問題連起來想想,想通了,你的思維勢如破竹!
在調用函數時,程序同樣會把實參傳入,在函數存儲區保存起來,如果有很多參數,將一起保存起來。
這時候就要用到va_list 了,這是個類型定義,我們可以把它理解成一個指針,它指向第一個參數的地址。
如果,我們這樣定義: va_list pp ;
則pp 就是這樣一種變量,它是指向所有參數中的第一個參數的。它不同於一般的指針變量,它是個復合變量,什麼是復合變量啊?結構體類型的嘛,呵呵。如果 a 是第一個參數,能不能寫成 pp=a 呢?
假設我定義了char d[]="ruixin",e[]="gelin"; 我要把 e 的值賦給 d ,能不能寫成 d=e 呢?得用 strcpy() ,是吧!呵呵,一樣的道理,這兒我們也用一個函數來實現,它就是 va_start();
如果這樣寫:va_start(pp,a);
那麼pp 就指向第一個參數 a 了,並且可得到 a 的類型 int 。
這時候如果有下一個參數,就需要使pp 指向下一個參數,並且得到它的類型。同樣需要使用函數來實現,這個函數是: va_arg()
可以這樣寫:va_arg(pp, 類型 ) ,這樣 pp 就指向一個參數,並且可以得到那個參數的類型了。
注意!類型非常重要,學過指針的都應該清楚,指針的類型如果弄錯的話,位置正確,取出來的數可能也是亂七八糟的。
下面我們看一個簡單的例子:
1 #include <stdio.h> 2 #include<stdarg.h> 3 4 void fun(int a,...) 5 { 6 va_list pp; 7 int n=1;//使用 n 計量參數個數 8 va_start(pp,a); 9 do 10 { 11 printf("第 %d 個參數 =%d/n",n++,s); 12 a=va_arg(pp,int);//使 pp 指向下一個參數,將下一個參數的值賦給變量 a 13 } 14 while (a!=0);//直到參數為 0 時停止循環 15 } 16 17 main() 18 { 19 20 fun(20,40,60,80,0); 21 }
注意!
一定要有上面兩個文件包含命令,因為程序中用到的那3個點都在那個文件裡。其實真正意義上應該說那是函數,實質上那不過是兩個宏
VA_LIST 是在C語言中解決變參問題的一組宏
VA_LIST的用法:
(1)首先在函數裡定義一具VA_LIST型的變量,這個變量是指向參數的指針
(2)然後用VA_START宏初始化變量剛定義的VA_LIST變量,這個宏的第二個參數是第一個可變參數的前一個參數,是一個固定的參數。
(3)然後用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型。
(4)最後用VA_END宏結束可變參數的獲取。然後你就可以在函數裡使用第二個參數了。如果函數有多個可變參數的,依次調用VA_ARG獲取各個參數。
VA_LIST在編譯器中的處理:
1)在運行VA_START(ap,v)以後,ap指向第一個可變參數在堆棧的地址。
(2)VA_ARG()取得類型t的可變參數值,在這步操作中首先apt = sizeof(t類型),讓ap指向下一個參數的地址。然後返回ap-sizeof(t類型)的t類型*指針,這正是第一個可變參數在堆棧裡的地址。然後用*取得這個地址的內容。
(3)VA_END(),X86平台定義為ap = ((char*)0),使ap不再指向堆棧,而是跟NULL一樣,有些直接定義為((void*)0),這樣編譯器不會為VA_END產生代碼,例如gcc在Linux的X86平台就是這樣定義的。
要注意的是:由於參數的地址用於VA_START宏,所以參數不能聲明為寄存器變量,或作為函數或數組類型。
使用VA_LIST應該注意的問題:
(1)因為va_start, va_arg,
va_end等定義成宏,所以它顯得很愚蠢,可變參數的類型和個數完全在該函數中由程序代碼控制,它並不能智能地識別不同參數的個數和類型.
也就是說,你想實現智能識別可變參數的話是要通過在自己的程序裡作判斷來實現的.
(2)另外有一個問題,因為編譯器對可變參數的函數的原型檢查不夠嚴格,對編程查錯不利.不利於我們寫出高質量的代碼。
小結:可變參數的函數原理其實很簡單,而
VA系列是以宏定義來定義的,實現跟堆棧相關。我們寫一個可變函數的C函數時,有利也有弊,所以在不必要的
場合,我們無需用到可變參數,如果在C++裡,我們應該利用C++多態性來實現可變參數的功能,盡量避免用C語言的方式來實現。
轉載自:http://blog.csdn.net/aking1314/article/details/5874195