通過printf函數簡單實現解析不定參數表

代碼先行,分析在後。

#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.避免出現異常.注意:,應該養成,關閉指針的習慣.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章