C语言可变参数函数详解示例

 更新时间:2013年11月14日 10:05:23   作者:  
一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的实际参数。但在某些情况下我们希望函数的参数个数可以根据需要确定,因此c语言引入可变参数函数。典型的可变参数函数的例子有printf()、scanf()等,下面我就开始讲解

先看代码

复制代码 代码如下:

printf(“hello,world!”);其参数个数为1个。
printf(“a=%d,b=%s,c=%c”,a,b,c);其参数个数为4个。

如何编写可变参数函数呢?我们首先来看看printf函数原型是如何定义的。
在linux下,输入man 3 printf,可以看到prinf函数原型如下:
复制代码 代码如下:

SYNOPSIS
#include <stdio.h>
int printf(const char *format, ...);

后面的三个点...表示printf参数个数是不定的.
如何实现可变参数函数?
2. 编写可变函数准备
为了编写可变参数函数,我们通常需要用到<stdarg.h>头文件下定义的以下函数:
复制代码 代码如下:

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

其中:
va_list是用于存放参数列表的数据结构。
va_start函数根据初始化last来初始化参数列表。
va_arg函数用于从参数列表中取出一个参数,参数类型由type指定。
va_copy函数用于复制参数列表。
va_end函数执行清理参数列表的工作。
上述函数通常用宏来实现,例如标准ANSI形式下,这些宏的定义是:
复制代码 代码如下:

typedef char * va_list; //字符串指针
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

使用宏_INTSIZEOF是为了按照整数字节对齐指针,因为c调用协议下面,参数入栈都是整数字节(指针或者值)。
函数官方说明,如果你看到英文就烦,可以自行忽略以下说明。
va_start()
       The  va_start() macro initializes ap for subsequent use by va_arg() and
       va_end(), and must be called first.
       The argument last is the name of the last argument before the  variable
       argument list, that is, the last argument of which the calling function
       knows the type.
       Because the address of this argument may  be  used  in  the  va_start()
       macro,  it should not be declared as a register variable, or as a func‐
       tion or an array type.
va_arg()
       The va_arg() macro expands to an expression that has the type and value
       of  the  next  argument in the call.  The argument ap is the va_list ap
       initialized by va_start().  Each call to va_arg() modifies ap  so  that
       the  next  call returns the next argument.  The argument type is a type
       name specified so that the type of a pointer to an object that has  the
       specified type can be obtained simply by adding a * to type.
       The  first use of the va_arg() macro after that of the va_start() macro
       returns the argument after last.   Successive  invocations  return  the
       values of the remaining arguments.
       If  there  is  no  next argument, or if type is not compatible with the
       type of the actual next argument (as promoted according to the  default
       argument promotions), random errors will occur.
       If  ap is passed to a function that uses va_arg(ap,type) then the value
       of ap is undefined after the return of that function.
va_end()
       Each invocation of va_start() must be matched by a corresponding  invo‐
       cation of va_end() in the same function.  After the call va_end(ap) the
       variable ap is undefined.  Multiple traversals of the list, each brack‐
       eted  by va_start() and va_end() are possible.  va_end() may be a macro
       or a function.
GNU给出的一个实例:
复制代码 代码如下:

#include <stdio.h>
#include <stdarg.h>
void
foo(char *fmt, ...)
{
  va_list ap;
  int d;
  char c, *s;
 va_start(ap, fmt);
 while (*fmt)
     switch (*fmt++) {
     case 's': /* string */
     s = va_arg(ap, char *);
         printf("string %s\n", s);
         break;
     case 'd': /* int */
         d = va_arg(ap, int);
         printf("int %d\n", d);
         break;
     case 'c': /* char */
/* need a cast here since va_arg only takes fully promoted types */
        c = (char) va_arg(ap, int);
        printf("char %c\n", c);
        break;
   }
   va_end(ap);
}

说明:
va_start(ap, fmt);用于根据fmt初始化可变参数列表。
va_arg(ap, char *);用于从参数列表中取出一个参数,其中的char *用于指定所取的参数的类型为字符串。每次调用va_arg后,参数列表ap都会被更改,以使得下次调用时能得到下一个参数。
va_end(ap);用于对参数列表进行一些清理工作。调用完va_end后,ap便不再有效。
以上程序给了我们一个实现printf函数的是思路,即:通过调用va_start函数,来得到参数列表,然后我们一个个取出参数来进行输出即可。
3.实例
例如:对于printf(“a=%d,b=%s,c=%c”,a,b,c)语句;fmt的值为a=%d,b=%s,c=%c,调用va_start函数将参数a,b,c存入了ap中。注意到:fmt中的%为特殊字符,紧跟%后的参数指明了参数类型.
因此我们的简易printf函数如下:
复制代码 代码如下:

#include <stdio.h>
#include <stdarg.h>
void
myprintf(char *fmt, ...)
{
  va_list ap;
  int d;
  double f;
  char c;
  char *s;
  char flag;
  va_start(ap,fmt);
  while (*fmt){
   flag=*fmt++;
   if(flag!='%'){
 putchar(flag);
 continue;
  }
  flag=*fmt++;//记得后移一位
    switch (flag)
  {
   case 's':
 s=va_arg(ap,char*);
 printf("%s",s);
 break;
   case 'd': /* int */        
 d = va_arg(ap, int);        
 printf("%d", d);        
 break;    
   case 'f': /* double*/        
 d = va_arg(ap,double);        
 printf("%d", d);        
 break;
   case 'c': /* char*/  
 c = (char)va_arg(ap,int);       
 printf("%c", c);       
 break;
   default:
 putchar(flag);
 break;
  }  
  }
  va_end(ap);
}
int main(){
  char str[10]="linuxcode";
  int i=1024;
  double f=3.1415926;
  char c='V';
  myprintf("string is:%s,int is:%d,double is:%f,char is :%c",str,i,f,c);
}

从上面我们可以知道可变参数函数的编写,必须要传入一个参数fmt,用来告诉我们的函数怎样去确定参数的个数。我们的可变参数函数是通过自己解析这个参数来确定函数参数个数的。
比如,我们编写一个求和函数,其函数实现如下:
复制代码 代码如下:

int sum(int cnt,...){
    int sum=0;
int i;
    va_list ap;
    va_start(ap,cnt);
for(i=0;i<cnt;++i)
 sum+=va_arg(ap,int);
    va_end(ap);
return sum;
}

总结一下就是:通过va_start初始化参数列表(也就能得到具体的参数个数了),然后使用va_arg函数从参数列表中取出你想要的参数,最后调用va_end执行清理工作。

相关文章

  • 探讨:用两个栈实现一个队列(我作为面试官的小结)

    探讨:用两个栈实现一个队列(我作为面试官的小结)

    作为面试官的我,经常拿这道用两个栈实现一个队列的面试题来考面试者,通过对面试者的表现和反应,有一些统计和感受,在此做个小结
    2013-05-05
  • C语言动态内存的分配实例详解

    C语言动态内存的分配实例详解

    动态内存管理同时还具有一个优点,当程序在具有更多内存的系统上需要处理更多数据时,不需要重写程序,下面这篇文章主要给大家介绍了关于C语言动态内存分配的相关资料,需要的朋友可以参考下
    2022-06-06
  • Qt项目打包的实现步骤

    Qt项目打包的实现步骤

    本文主要介绍了Qt项目打包的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C语言利用面试真题理解指针的使用

    C语言利用面试真题理解指针的使用

    C语言这门课程在计算机的基础教学中一直占有比较重要的地位,然而要想突破C语言的学习,对指针的掌握是非常重要的,本文将具体针对指针的基础做详尽的介绍
    2022-08-08
  • 递归删除二叉树中以x为根的子树

    递归删除二叉树中以x为根的子树

    今天小编就为大家分享一篇关于递归删除二叉树中以x为根的子树,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • C++ Clock类模拟实现闹钟运行

    C++ Clock类模拟实现闹钟运行

    这篇文章主要为大家详细介绍了C++ Clock类模拟实现闹钟运行,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言实现动态版通讯录的代码分享

    C语言实现动态版通讯录的代码分享

    这篇文章主要为大家详细介绍了如何利用C语言实现一个简单的动态版通讯录,主要运用了结构体,一维数组,函数,分支与循环语句等等知识,需要的可以参考一下
    2023-01-01
  • C语言 scanf的工作原理详解

    C语言 scanf的工作原理详解

    这篇文章主要为大家介绍了C语言 scanf的工作原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • C++类与对象的重点知识点详细分析

    C++类与对象的重点知识点详细分析

    类和对象是两种以计算机为载体的计算机语言的合称。对象是对客观事物的抽象,类是对对象的抽象。类是一种抽象的数据类型;变量就是可以变化的量,存储在内存中—个可以拥有在某个范围内的可变存储区域
    2023-02-02
  • VS2019配置opencv详细图文教程和测试代码的实现

    VS2019配置opencv详细图文教程和测试代码的实现

    这篇文章主要介绍了VS2019配置opencv详细图文教程和测试代码的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04

最新评论