【可变参数函数——printf】
2015-12-29 00:00
381 查看
可变参数是C语言中一个比较高级的应用。使用可变参数用户可以在调用函数的时候再确定该函数所需要的参数。
1.可变参数的概念:
在编程过程中使用的printf函数就是一个典型的参数可变的函数。函数原型如下:
int printf(const char* format,...);其中第一个参数format是固定的,后面的参数个数和类型是可变的。编译器使用三个点“...”作为参数的占位符。告知编译器第一个参数format后面还可能会有若干的参数。
2.可变参数函数实现原理浅析
(1)C函数调用的栈结构
可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈顺序规则从右到左,即函数中的最右边的参数先入栈。例如对于函数:
void fun(int a,int b,int c){
int d;
.........
}
其栈结构为:
0x1ffc-->d
0x2000-->a
0x2004-->b
0x2008-->c
对于任何编译器,每个栈单元的大小都是sizeof(int).
因此我们可以知道,函数的所有参数是存储在线性的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。
下面就来说说C语言中一个好东西——宏。C语言中就是通过宏的高级应用来实现变参的寻址。
因此,根据stdarg.h头文件所定义的宏,可以总结出实现一个可变函数设计时所需要的步骤:
(1)在程序中依次用到以下这些宏
void va_start(va_list ap,A);
type va_arg(va_list ap,T);
void va_end(va_list ap);
( 2 )函数里首先定义一个va_list型的变量,这里是ap,这个变量是存储参数地址的指针。因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
typedef char * va_list;//内核中定义好了
直接定义:va_list ap
(3)然后用va_start宏初始化(2)中定义的变量ap,这个宏的第二个参数是可变参数列表的前一个参数,就是那个固定参数。
#define va_start(ap,format) (ap=(va_list)&format+_INTSIZEOF(format))
(4)然后依次用va_arg宏使ap返回可变参数地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。
(5)设定结束条件。由于被调用的函数在调用时一般是不知道可变参数的正确数目的,我们必须自己在代码中指明结束条件。
printf函数太强大了,目前只实现了一点小小的功能,帮助理解下了原理
1.可变参数的概念:
在编程过程中使用的printf函数就是一个典型的参数可变的函数。函数原型如下:
int printf(const char* format,...);其中第一个参数format是固定的,后面的参数个数和类型是可变的。编译器使用三个点“...”作为参数的占位符。告知编译器第一个参数format后面还可能会有若干的参数。
2.可变参数函数实现原理浅析
(1)C函数调用的栈结构
可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈顺序规则从右到左,即函数中的最右边的参数先入栈。例如对于函数:
void fun(int a,int b,int c){
int d;
.........
}
其栈结构为:
0x1ffc-->d
0x2000-->a
0x2004-->b
0x2008-->c
对于任何编译器,每个栈单元的大小都是sizeof(int).
因此我们可以知道,函数的所有参数是存储在线性的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。
下面就来说说C语言中一个好东西——宏。C语言中就是通过宏的高级应用来实现变参的寻址。
因此,根据stdarg.h头文件所定义的宏,可以总结出实现一个可变函数设计时所需要的步骤:
(1)在程序中依次用到以下这些宏
void va_start(va_list ap,A);
type va_arg(va_list ap,T);
void va_end(va_list ap);
( 2 )函数里首先定义一个va_list型的变量,这里是ap,这个变量是存储参数地址的指针。因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
typedef char * va_list;//内核中定义好了
直接定义:va_list ap
(3)然后用va_start宏初始化(2)中定义的变量ap,这个宏的第二个参数是可变参数列表的前一个参数,就是那个固定参数。
#define va_start(ap,format) (ap=(va_list)&format+_INTSIZEOF(format))
(4)然后依次用va_arg宏使ap返回可变参数地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。
(5)设定结束条件。由于被调用的函数在调用时一般是不知道可变参数的正确数目的,我们必须自己在代码中指明结束条件。
printf函数太强大了,目前只实现了一点小小的功能,帮助理解下了原理
#include <stdio.h> #include <stdarg.h> #include <string.h> #define BUFSIZE 12 //将数字转化为字符存在buf中,方便用putchar输出 void number(char *buf,int num) { int cnt = 0 , fact = 0; fact = num; char tmp; while(fact){ buf[cnt++] = fact%10 + '0'; fact /= 10; } fact = cnt - 1; cnt = 0; while(fact>cnt){ tmp= buf[fact]; buf[fact]=buf[cnt]; buf[cnt]=tmp; fact--; cnt++; } } ch_print(const char *line) { const char *cp = line; while((*cp != '\0') && (*cp != '\n')){ putchar(*cp); cp++; } } void west_print(const char *fmt,...) { va_list para; const char *step = fmt , *pnt; char tmp; char buf[BUFSIZE] = {'\0'}; va_start(para,fmt); //准备访问可变参数 while(*step){ //一个一个遍历固定参数中的字符 if(*step != '%'){ putchar(*step); }else{ ++step; switch(*step){ /* char */ case 'c': tmp = (char)va_arg(para,int); //参数列表可变参数的类型 putchar(tmp); break; /* integer */ case 'd': memset(buf,0x00,BUFSIZE); number(buf,(int)(va_arg(para,int)) );//将数字都转化为字符存在buf中方便后面输出 ch_print(buf); //输出buf中的内容 break; /* string */ case 's': pnt = (char *)va_arg(para,char *); ch_print(pnt); break; /* % self */ case '%': putchar('%'); //这个相当于连着两个% break; /* Other cases */ default: putchar(*step); //其他的正常输出 break; }//end switch }//end else[if(!='%')] /* point for the next character */ step++; }//end while[!='\0'] } int main() { int test1 = 123456; char test2 = 'A'; char test3[] = "hello world !"; west_print("test print : %% - %d,%c,%s\n",test1,test2,test3); }
相关文章推荐
- Python 源码的考古(二) 读 0.9.1 源码
- Python 源码的考古(三) 读 0.9.1 源码2
- Ubuntu server 14.04 安装 ITDB资产管理工具
- 我的Python 学习之旅 从0开始的小白
- 最新最全的Python 学习路线图
- 关于网页设计的真相你不得不知
- 想要征服Spark吗?这几个经典课程必学!
- android动画
- PYTHONPATH
- iOS 9之应用内搜索(CoreSpotlight)API
- NSTimer
- maven打包跳过Test
- logrotate日志管家来切割日志(tomcat,nginx,httpd的日志)
- windows bat 常用命令
- 天下数据教你如何应付ARP病毒
- 关于为什么IE浏览器无法打开开发者模式的解决办法
- 使用git建立远程仓库,让别人git clone下来
- svn使用socks5代理 ProxyChains
- nginx 开发一个简单的 HTTP 模块
- MySQL新增字段、修改字段位置和查询存储过程的SQL代码