C语言可变参数函数及三个宏va_start、va_arg和va_end的使用

来源:互联网 发布:javascript考试题目 编辑:程序博客网 时间:2024/04/28 16:33


一、可变参数函数的实例
大家熟知的printf()函数声明如下:
    int  printf(const char * format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是
可变的,例如我们可以有以下不同的调用方法:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);


二、如何编写一个自已的可变参数函数.
查了一下,在<stdarg.h>中定义了三个宏va_start()、va_arg()和va_end()用于实现可变参数。

  void va_start( va_list arg_ptr, prev_param );
  type va_arg( va_list arg_ptr, type );
  void va_end( va_list arg_ptr );

试编了一个:

 

#include <stdio.h>
#include <stdarg.h>
#define ENDING_INT 0


int SumAll(int number1,...)  //把参数加总
{
    va_list arg_pointer; //首先定义一个va_list型的变量,这个变量是指向参数的指针.

    int current_number;  //当前的数字
    int total;           //数字之和

    //用va_start初始化变量arg_pointer,这个宏的第二个参数是第一个可变参数(一个固定的参数)
    va_start(arg_pointer,number1);
    total=number1;

    do
      {
        //用va_arg返回后续的可变参数, 类型是 int
        current_number=va_arg(arg_pointer,int);
        total += current_number;
      }
    while (current_number!=ENDING_INT);   //如果参数是结束标识(这里是ENDING_INT),则结束
    va_end(arg_pointer); //结束参数列表

    return total;
}


int main(int argc, char* argv[])
{
   int n;

   n=SumAll(100,200,ENDING_INT);   //返回结果是300
   printf("%d /n",n);

   n=SumAll(100,200);  //由于没有结束标识,返回结果不确定
   printf("%d /n",n);
}

 

  因为va_start, va_arg, va_end等定义成宏,所以可变参数的类型和个数需要由程序代码控制。
一般来说,设一个结束标识,这里是 ENDING_INT。用它来识别不同参数的个数。

  SumAll(100,200,ENDING_INT);   //调用方式正确,返回结果是300

  SumAll(100,200);  //调用方式不正确,由于没有结束标识,返回结果不确定。


三、理解va_start、va_arg和va_end


看一下<stdarg.h>中宏的定义

 

定义:typedef char * va_list;
理解:va_list 就是一个指针,指向参数列表。


定义:#define _INTSIZEOF(n)   ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
定义:#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
理解:va_start 宏, 就是把ap赋值为参数v起始的参数列表的下一个参数

 

定义:#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
理解:va_arg宏,就是把ap赋值为下一个参数

 

定义:#define va_end(ap) ( ap = (va_list)0 )
理解:va_end宏,就是把ap赋值为空(0)

 

  从va_*的实现可以看出,充分运用指针,把C语言的灵活特性表现得淋漓尽致。当然,用不好也容易出错。
va_*中,为了得到所有传递给函数的参数,需要用va_arg依次遍历。但是有两个要求:

(1)要确定参数的类型。
    一般来说,各个参数的类型是一样的。

(2)要有结束标志。如果没有结束标志,va将按默认类型依次返回内存中的内容,直到访问到非法内存而出错退出。
     所以上述的调用 SumAll(100,200);  返回的结果是不确定的。


四、再写一个参数类型是 char * 的 可变参数函数.

#include <stdio.h>
#include <string.h>
#include <alloc.h>
#include <stdarg.h>

 

#define ENDING_STRING NULL

 

//把多个字符串连接起来
char *StrCat(char *src,...)
{
    va_list     va;
    const char  *src_pointer;
    char        *dest;         /*  结果字符串 */
    size_t       dest_size;     /*  结果字符串的大小*/

    /* 计算字符串的大小 */
    va_start (va, src);             /*  开始变长参数处理 */
    src_pointer   = src;
    dest_size = 1;
    while (src_pointer!=ENDING_STRING)  /* ENDING_STRING == NULL */
      {
        dest_size += strlen (src_pointer);
        src_pointer = va_arg (va, char *);  /* 取下一个参数 */
      }
    va_end (va);                    /*  结束变长参数处理  */

    /*  申请内存 */
    dest = malloc( dest_size );
    if (dest == NULL) return (NULL);

    /*  逐个复制字串到结果字符串  */
    va_start (va, src);       /*  开始变长参数处理 */
    src_pointer  = src;
    dest [0] = '/0';         /* 先设置为空串 */
    while (src_pointer!=ENDING_STRING)   /* ENDING_STRING == NULL */
      {
        strcat (dest, src_pointer);      /*  复制字串到结果字符串  */
        src_pointer = va_arg (va, char *);
      }
    va_end (va);                      /*  结束变长参数处理  */
    return (dest);
}

 

int main(int argc, char* argv[])
{
   int n;
   char *s;

   s=StrCat("hello"," ","world",NULL);
   printf("%s /n",s);   //返回结果是 hello world

}


  嗯,还是比较好用的,千万不要忘记: 调用时要加上结束标识符哦。

  可能要问,为什么 printf() 函数调用时没有结束标识符呢?

  了解了一下 printf()的源码,我是这样理解的。
    printf(char *format,...) 中,在format参数中,就可以判断出后续参数的个数和类型,因此不需要结束标识符就可以知道参数的个数了。比如:
   printf( "%s %d", "hello", 1);
   "%s %d"表明后续参数个数为2个,第一个是 string类型,第二个是 int 类型。

 

试一下,如果写成这样
  printf( "%s %d %d", "hello", 1);
  

 "%s %d %d"表明后续参数个数为3个,实际上是两个,由于缺了一个,返回结果是不可确定的。

 

呵呵,调用printf()是要小心出错。