热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的实际参数。但在某些情况下我们希望函数的参数个数可以根据需要确定,因此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
int printf(const char *format, ...);

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

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
#include
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
#include
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 sum+=va_arg(ap,int);
    va_end(ap);
return sum;
}

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


推荐阅读
  • 在尝试使用Ubuntu 10.04进行Android开发时,遇到了系统无法识别HTC G1设备的情况。本文将详细介绍如何通过配置系统和安装必要的驱动来解决这一问题。 ... [详细]
  • 本文详细介绍了如何使用 Python 编程语言中的 Scapy 库执行 DNS 欺骗攻击,包括必要的软件安装、攻击流程及代码示例。 ... [详细]
  • 最新进展:作为最接近官方声明的信息源,本文吸引了大量关注。若需获取最新动态,请访问:lkhill.com/ccie-version-5-update ... [详细]
  • Kubernetes 实践指南:初次体验
    本文介绍了如何通过官方提供的简易示例,快速上手 Kubernetes (K8S),并深入理解其核心概念和操作流程。 ... [详细]
  • 微服务自动化.dockercompose
    目录一、docker-compose二、docker-compose安装与配置1、修改docker.service2、下载文件3、将刚才下载的docker-compose文 ... [详细]
  • 本文探讨了如何在Linux系统中利用iptables和NFQUEUE处理来自镜像端口的流量。当镜像端口的数据包目的MAC地址与主机MAC地址不符时,通常会导致流量无法被NFQUEUE捕获。文章提供了解决这一问题的方法。 ... [详细]
  • 分布式计算助力链力实现毫秒级安全响应,确保100%数据准确性
    随着分布式计算技术的发展,其在数据存储、文件传输、在线视频、社交平台及去中心化金融等多个领域的应用日益广泛。国际知名企业如Firefox、Google、Opera、Netflix、OpenBazaar等均已采用该技术,推动了技术创新和服务升级。 ... [详细]
  • 在Linux系统中使用EncFS实现文件夹加密
    为了保护个人隐私或敏感数据不被未经授权的访问,可以通过加密技术来增强安全性。本文介绍如何在Linux系统上使用EncFS工具创建和管理加密文件夹,以确保即使在系统登录状态下,特定文件夹中的数据也保持加密状态。 ... [详细]
  • 本文概述了在GNU/Linux系统中,动态库在链接和运行阶段的搜索路径及其指定方法,包括通过编译时参数、环境变量及系统配置文件等方式来控制动态库的查找路径。 ... [详细]
  • 本文概述了作者在2014年的几项目标与愿望,包括职业发展、个人成长及家庭幸福等方面的具体计划。 ... [详细]
  • Linux系统中的USB驱动架构
    USB系统采用了树状拓扑结构,其主机端和设备端分别配置了主机控制器(Host Controller)和USB设备控制器(USB Device Controller)。主机控制器负责整个USB系统的通信调度,确保数据的有效传输。 ... [详细]
  • 本文探讨了在使用 MyBatis 进行批量数据处理时遇到的参数绑定异常问题,并提供了详细的解决方案。 ... [详细]
  • 本文详细介绍了如何在Windows和Linux系统上配置Openfire服务器,包括安装步骤、数据库配置及端口映射等关键环节。 ... [详细]
  • 本文详细介绍了如何使用Linux下的mysqlshow命令来查询MySQL数据库的相关信息,包括数据库、表以及字段的详情。通过本文的学习,读者可以掌握mysqlshow命令的基本语法及其常用选项。 ... [详细]
  • 本文定期更新,涵盖虚拟化技术的基础知识、Xen虚拟机架构详解、KVM架构与原理、QEMU模拟器的功能及使用方法。同时,文章还探讨了不同虚拟化技术之间的比较与联系,以及如何利用这些技术进行网络配置和虚拟磁盘管理。 ... [详细]
author-avatar
东添好冷2011_258
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有