先看代码

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调用协议下面,参数入栈都是整数字节(指针或者值)。

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;
}

原文博客:http://www.jb51.net/article/43192.htm

va_list使用vprintf()实例:

#include <stdio.h>
#include <stdarg.h> void my_print(const char* format, ...)
{
int n;
va_list arg_list;
va_start(arg_list, format);
n = vprintf(format, arg_list);
va_end(arg_list);
} int main(int argc, char *argv[])
{
int ai = 0; my_print("%d\n", ai); return 0;
}

  

C语言可变参数函数详解示例的更多相关文章

  1. 【C语言】printf函数详解

    C语言printf函数详解 一.相关基础知识 请求printf()打印变量的指令取决于变量的类型,例如打印整数用%d符号,打印字符用%c符号,这些符号称为转换说明(conversion specifi ...

  2. C语言可变参数函数实现原理

    一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...

  3. C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...

  4. c语言可变参数函数

    c语言支持可变参数函数.这里的可变指,函数的参数个数可变. 其原理是,一般情况下,函数参数传递时,其压栈顺序是从右向左,栈在虚拟内存中的增长方向是从上往下.所以,对于一个函数调用 func(int a ...

  5. c语言文件操作函数详解

    一.文件操作注意点: 1 打开文件时,如果打开方式加“+”,表示该文件可以“写” ; 2 退出程序一般用exit函数,正常退出参数为0,非正常退出参数为正零值 ; 3 文件的读写操作:按字符.字符串. ...

  6. 【C语言】printf()函数详解

    printf函数称为格式输出函数,其关键字最末一个字母f即为"格式"(format)之意.其功能是按用户指定的格式,把指定的数据显示到显示器屏幕上.在前面的例题中我们已多次使用过这 ...

  7. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  8. C语言memset函数详解

    C语言memset函数详解 memset() 的作用:在一段内存块中填充某个给定的值,通常用于数组初始化与数组清零. 它是直接操作内存空间,mem即“内存”(memory)的意思.该函数的原型为: # ...

  9. FFmpeg(2)-avformat_open_input()函数详解并示例打开mp4文件

    一. 解封装 pts 是显示的时间 dts是解码的时间, 这个时间是用来做同步. av_register_all(), 注册所有的格式.包括解封装格式和加封装格式. avformat_network_ ...

随机推荐

  1. 运维url收集

    https://www.centos.bz/tag/nagios/ Graphite的百万Metrics实践之路 饿了么 Influxdb 实践之路

  2. CAMediaTimingFunction的使用

    CAMediaTimingFunction的使用 CAMediaTimingFunction可以用在POP动画的自定义动画当中,算是非常实用的工具,当然,系统的动画也是可以使用的. 效果: 需要用到的 ...

  3. Java学习---InetAddress类的学习

    基础知识 1.InetAddress类 在网络API套接字,InetAddress类和它的子类型对象使用域名DNS系统,处理主机名到主机IPv4或IPv6地址的转换.如图1-1所示. 由于InetAd ...

  4. Asp.Net MVC Identity 2.2.1 使用技巧(六)

    使用用户管理器之角色管理 一.建立模型,这里我们其实在之前的技巧(五)已经建好了. 二.建立控制器RolesAdminController 1.在controllers文件夹上点右键>添加> ...

  5. iOS亮屏解锁命令【iOS自动化测试】--使用ssh

    前提:iOS越狱手机一个 越狱方法:使用pp助手, 爱思助手等 1.从Cydia安装以下软件: AppSync for iOSx(可安装破解软件).afc2add补丁(可访问整个iOS设备的系统文件) ...

  6. 9、Node.js Stream(流)

    #########################################################################介绍Node.js Stream(流)Stream 是 ...

  7. 深入理解php中的ini配置(2)

    继续接着上一篇写. 运行时改变配置 在前一篇中曾经谈到,ini_set函数可以在php执行的过程中,动态修改php的部分配置.注意,仅仅是部分,并非所有的配置都可以动态修改.关于ini配置的可修改性, ...

  8. CXF+JAXB处理复杂数据

    CXF+JAXB处理复杂数据   CXF默认使用JAXB 来实现对象和XML之间的映射.在前面的例子 中,使用CXF发布的Webservice,其方法的参数和返回值都是简单类型. 本文讨论对象复杂性的 ...

  9. AngularCLI介绍及配置文件主要参数含义解析

    使用Angular CLI可以快速,简单的搭建一个angular2或angular4项目,是只要掌握几行命令就能构建出前端架构的最佳实践,它本质也是使用了webpack来编译,打包,压缩等构建的事情, ...

  10. POJ2074 Line of Sight

    嘟嘟嘟 题意:用一条水平线段表示以栋房子:\((x_0, y_0)(x_0', y_0)\).然后有一条低于房子的水平线段\(l_0\),代表你可以到的位置.接下来输入一个数\(n\),一下\(n\) ...