代碼先行,分析在後。
#include<stdarg.h>
/*minprintf:minimal printf with variable argument list */
void minprintf(char *fmt,...)
{
va_list ap; /*points to unnamed arg in turn */
char *p, *sval;
int ival;
double dval;
va_start(ap,fmt); /*make ap point to 1st unamed 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':
ival = va_arg(ap ,double);
printf("%f", ival);
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*/
}
在C語言中,沒有函數重載。所以要想實現不定數目的函數參數,變得比較複雜。瞭解這個問題,涉及到標準頭文件<stdarg,h>幾個宏定義,
va_start和va_end 等。在此之前,先看一下C語言中傳遞函數的參數時的用法和原理:
1 在c中,當我們無法列出傳遞函數的所有實參的類型和數目時,,可以用省略號指定參數列表。
void foo(...);
void foo(parm_list,..);
這就是C中一種傳參的形式,多用於變長參數表。
2.函數參數傳遞的原理
函數參數是以數據結構:棧的形式存取,從右至左入棧。這跟棧的機制有關。
參數的內存存放格式:參數存放在內存的堆棧段中,在執行函數時,從最後一個開始入棧。因此棧底的高地址,棧頂低地址。void func(int x,float y,char z),
在函數調用的時候,是參char z先進棧,然後是 floaty,intx,出的時候順序是相反的。理論上說,如果我們找到任意變量的地址,並知道其他變量的類型,便可以通過移動位置(指針移位運算,找到其他的輸入變量。
下面看幾個宏定義
typedef char* va_list;
void va_start(va_list ap, prev_param):/ANSI version*/
type va_arg(va_list ao,type);
void va_end(va_list ap);
va_list是一個字符指針,可以理解爲當前參數的一個指針,取參必須通過這個指針進行。
1在調用參數表之前,定義一個va_list類型的變量(假設va_list類型變量被定義爲ap);
2然後應該對ap進行初始化,讓它指向可變參數列表的第一個參數,是通過va_start實現的,第一個參數是ap本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數。
3 獲取參數,調用va_arg,他的第一個參數是ap,第二個參數是獲取的參數的指定類型,然後返回這個指定類型的值,並且把ap指向參數的下一個變量位置
4 獲取所有參加之後,我們有必要將ap指針關掉,調用va_end ,他是將ap指向爲空,應該養成取完參數之後,關閉指針的習慣,一般情況下,vstart 和 vend 同時出現。
如下面的小例子
#include <stdio.h>
void fun(int a,..)
{
int *temp = &a;
temp++;
for(int i = 0; i < a; ++i)
{
printf("%d",*temp);
temp++;
}
}
int main()
{
int a=1,
int b=2;
int c=3;
int d=4;
fun(4,a,b,c,d);
return 0;
}
獲取省略號指定的參數
在函數體聲明一個va_list,然後用va_start函數獲取參數列表中的參數,使用完畢後調用va_end()結束,例如:
void TestFun(char *pszDest, int DestLen, const char* pszFormat,...)
{
va_list args;
va_start(args,pszFormat); //"一定要"..."之前的那個參數
-vsnprintf(pszDest,Destlen,pszFormat,args);
va_end(args);
5.如何使用參數個數可變的函數,
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
/*注意函數原型聲明,至少有一個確定的參數,後面跟省略號*/
int demo(char,...)
void main(void)
{
demo("DEMO","This","is","a","demo!"," ");
}
int demo(char msg,...)
{
/*定義保存函數參數的結構*/
va_list argp;
int argno = 0;
char para;
/*argp 指向第一個可選參數,msg是最後一個確定的參數*/
va_start(argp,msg);
{
while(1)
{
para = va_arg(argp,char);
if(strcmp(para ," ") == 0)
break;
printf("Parameter #%d is :%s\n",argno,para);
argno ++;
}
va_end(argp)
retrun 0;
}
6 回過頭來看一下一開始那個程序.
minprintf函數用來遍歷printf函數的參數表,它的參數爲printf函數中參數的指針
指針ap 用來實現遍歷函數的參數列表.在函數運行中ap會先後指向參數表中的每一個參數.
va_start(ap,fmt).ap指向省略中的第一個參數.fmt指向最後一個函數參數表中有名參數.即開始時ap指向的參數的前一個參數.
for(p =fmt;*p;p++),p初始爲*fmt ,即指向ap前一個參數,通過對p的循環實現ap指針遍歷省略的參數表.
就有了下面的switch,來討論可能出現的參數.
最後通過va_end(ap),將ap指向NULL.避免出現異常.注意:,應該養成,關閉指針的習慣.