先看代码,

minprintf

#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语言中,没有函数重载。所以要想实现不定数目的函数参数,变得比较复杂。了解这个问题,涉及到标准头文件几个宏定义,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.避免出现异常.注意:,应该养成,关闭指针的习惯.

Top^

by 李鹏