C语言变参函数的编写
1. 引言
一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的实际参数。但在某些情况下我
们希望函数的参数个数可以根据需要确定,因此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);
}
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;
}
C语言变参函数的编写的更多相关文章
- C语言pow函数编写
C语言pow函数编写 #include<stdio.h> double chaoba(double f,double q); //声明自定义函数 void main(void) { dou ...
- C语言变参函数/Variadic fucntion
几个重要的 宏/类型 定义 Macros Defined in header <stdarg.h> va_start enables access to variadic function ...
- Verilog 语言 001 --- 入门级 --- 编写一个半加器电路模块
Verilog 语言编写一个 半加器 电路模块 半加器 的电路结构: S = A 异或 B C = A 与 B 1. 程序代码 module h_adder (A, B, SO, CO); input ...
- 听说awk语言也可以编写脚本
导读 从 awk 系列开始,我们都是在命令行或者脚本文件里写一些简短的 awk 命令和程序.然而 awk 和 shell 一样也是一个解释型语言.通过从开始到现在的一系列的学习,你现在能写可以执行的 ...
- C语言变参函数的实现原理
1. 变参函数简单示例 #include <stdarg.h> #include <stdio.h> int Accumlate(int nr, ...) { ; ; va_l ...
- C语言:自己编写的简易ftp客户端,包含(列表,进入目录,上传文件,下载文件,删除文件)功能
//简易ftp客户端#include <stdio.h> #include <string.h> #include <sys/types.h> #include & ...
- 用Kotlin语言重新编写Plaid APP:经验教训(I)
原文标题:Converting Plaid to Kotlin: Lessons learned (Part 1) 原文链接:http://antonioleiva.com/plaid-kotlin- ...
- 用Java语言编写一个简易画板
讲了三篇概博客的概念,今天,我们来一点实际的东西.我们来探讨一下如何用Java语言,编写一块简易的画图板. 一.需求分析 无论我们使用什么语言,去编写一个什么样的项目,我们的第一步,总是去分析这个项目 ...
- 第一个C语言编译器是怎样编写的?
首先向C语言之父Dennis MacAlistair Ritchie致敬! 当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编写的,有一些语言比如Clojure,Jython等是基于J ...
随机推荐
- UVA - 10014 - Simple calculations (经典的数学推导题!!)
UVA - 10014 Simple calculations Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & ...
- linux_vim_最佳快捷键
如何使用vi文本编辑器 vi由比尔·乔伊(Bill Joy)撰写,所有UNIX like均默认安装此文本编辑器.详细简介请点击维基中文. 1.首先复制一个文件到/tmp目录(本例中为复制根目录 ...
- 编程算法 - 区间调度问题 代码(C)
区间调度问题 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 有n项工作, 每项工作分别在s时间開始, 在t时间结束. 对于每项工作能够选择參与 ...
- Hibernate_10_继承的例子_单表
只是建一个表,所有属性都包括在此表.使用discriminator 到父和子类之间的区别. 1)父类(Article): public class Article { private Integer ...
- 【Hibernate步步为营】--映射合集汇总
前几篇文章具体讨论了对象模型到关系模型的转化方法,对映射关系做了具体的了解,Hibernate将对象模型转化为对应的关系模型是通过使用对应的映射来完毕的(相同也能够使用注解),对于对象之间的关系的转化 ...
- R.layout.main connot be resolved 和R.java消失
出现例如以下问题: 鼠标放到出代码上面: 分析问题: 1.查看R文件是否被生成.假设没有生成,则勾选build Automatically,然后Clean: 2.假设R文件已生成.则删除去掉代码中: ...
- C#操作 Advantage Database Server 数据库
相关下载 http://devzone.advantagedatabase.com/dz/content.aspx?key=31 1.安装数据库: Advantage Database Server ...
- Zepto
移动开发流量省起来之Zepto 事情是这样的:最近开发的一个手机网站客户反应访问起来特别慢,刷了半天才能看到页面,然后问我们是不是网站出问题了.于是我赶紧找了各种手机测试一下,没有问题,首先排除程序错 ...
- WaitHandle、AutoResetEvent、ManualResetEvent
多线程中的锁系统(三)-WaitHandle.AutoResetEvent.ManualResetEvent 介绍 本章主要说下基于内核模式构造的线程同步方式,事件,信号量. 目录 一:理论 二:Wa ...
- PHP 做 RSA 签名 生成订单(支付宝例子)
/组合签名 $a=time(); $b=substr($a, 1); //生成随机订单号 $orderid= $b.mt_rand(10000,99999); //合作身份者id,以2088开头的16 ...