




已阅读5页,还剩15页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
c语言中可变参数函数的设计c语言中可变参数函数的设计 c语言中可变参数函数的设计 - 最近想好好学学这个, 先把网上搜集得资料贴上. = 参数可变函数的实现(上) CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。 此文献给如我一般还在探索C语言之路的朋友们。 注:本文中测试程序的编译环境为win2000和VC6.0缘起:作为一个程序员,我没有写过参数可变的函数,我相信大部分朋友也没有涉及过,或者我的境界层次太低了。那么缘何我要去揭这一层面纱呢?因为好奇!我是个思维具有极大惰性的人,曾经识得参数可变函数,也懒得去深究,但是它的三点(函数声明时参数列表中的“”)却深刻的映入了我的记忆里,而且是带着若干个闪耀的问号。可是就在昨天,在拜读某君的高论时,它再一次出现了。我的资质真的是不太够,因为某君在谈到它时只是给出了中关于它的宏定义,我想大概在高手眼里,点这一下就神会了吧。可是他这么轻轻一点却使留在记忆里曾经的那几个问号无限的膨胀,以至于我这个又菜又懒的所谓程序员也萌生了莫大的好奇。 破题: 但凡所谓“实现”都是从没有到有的过程,但是我只是想去解惑它的实现,因为它原本就是好端端的正为成千上万的程序员们服务。 还是从我们熟悉的printf说起: 如果你是个C语言的程序员,无论你是初学者还是高高手,对于printf都不会陌生,甚至你已经用了无数次了。我已经说过我是个有极大惰性的人,所以每次用printf都是照本宣科,规规矩矩的按教科书上说的做,从来没有问过一个为什么,这就是所谓的“熟视无睹”吧。其实,printf函数是一个典型的参数可变的函数。在保证它的第一个参数是字符串的条件下,你可以输任意数量任意合法类型的参数。只要你在第一个字符串参数中使用了对应的格式化字符串,你就可以输出正确的值。这难道不是件很有趣的事吗?那它是怎么做到的?1,首先,怎么得到参数的值。对于一般的函数,我们可以通过参数对应在参数列表里的标识符来得到。但是参数可变函数那些可变的参数是没有参数标识符的,它只有“”,所以通过标识符来得到是不可能的,我们只有另辟途径。我们知道函数调用时都会分配栈空间,而函数调用机制中的栈结构如下图所示: | . | - | 参数2 | - | 参数1 | - | 返回地址 | - |调用函数运行状态| -可见,参数是连续存储在栈里面的,那么也就是说,我们只要得到可变参数的前一个参数的地址,就可以通过指针访问到那些可变参数。但是怎么样得到可变参数的前一个参数的地址呢?不知道你注意到没有,参数可变函数在可变参数之前必有一个参数是固定的,并使用标识符,而且通常被声明为char*类型,printf函数也不例外。这样的话,我们就可以通过这个参数对应的标识符来得到地址,从而访问其他参数变得可能。我们可以写一个测试程序来试一下:#include void va_test(char* fmt,.);/参数可变的函数声明 void main() int a=1,c=55; char b=b; va_test(,a,b,c);/用四个参数做测试 void va_test(char* fmt,.) /参数可变的函数定义,注意第一个参数为char* fmt char *p=NULL; p=(char *)&fmt;/注意不是指向fmt,而是指向&fmt,并且强制转化为char *,以便一个一个字节访问 for(int i = 0;i16;i+)/16是通过计算的值(参数个数*4个字节),只是为了测试,暂且将就一下 printf(%.4d ,*p);/输出p指针指向地址的值 p+; 编译运行的结果为 0056 0000 0066 0000 | 0001 0000 0000 0000 | 0098 0000 0000 0000 | 0055 0000 0000 0000 由运行结果可见,通过这样方式可以逐一获得可变参数的值。至于为什么通常被声明为char*类型,我们慢慢看来。2,怎样确定参数类型和数量通过上述的方式,我们首先解决了取得可变参数值的问题,但是对于一个参数,值很重要,其类型同样举足轻重,而对于一个函数来讲参数个数也非常重要,否则就会产生了一系列的麻烦来。通过访问存储参数的栈空间,我们并不能得到关于类型的任何信息和参数个数的任何信息。我想你应该想到了使用char *参数。Printf函数就是这样实现的,它把后面的可变参数类型都放到了char *指向的字符数组里,并通过%来标识以便与其它的字符相区别,从而确定了参数类型也确定了参数个数。其实,用何种方式来到达这样的效果取决于函数的实现。比如说,定义一个函数,预知它的可变参数类型都是int,那么固定参数完全可以用int类型来替换char*类型,因为只要得到参数个数就可以了。3,言归正传 我想到了这里,大概的轮廓已经呈现出来了。本来想就此作罢的(我的惰性使然),但是一想到如果不具实用性便可能是一堆废物,枉费我打了这么些字,决定还是继续下去。 我是比较抵制用那些不明所以的宏定义的,所以在上面的阐述里一点都没有涉及定义在的va(variable-argument)宏。事实上,当时让我产生极大疑惑和好奇的正是这几个宏定义。但是现在我们不得不要去和这些宏定义打打交道,毕竟我们在讨生计的时候还得用上他们,这也是我曰之为“言归正传”的理由。 好了,我们来看一下那些宏定义。 打开文件,找一下va_*的宏定义,发现不单单只有一组,但是在各组定义前都会有宏编译。宏编译指示的是不同硬件平台和编译器下用怎样的va宏定义。比较一下,不同之处主要在偏移量的计算上。我们还是拿个典型又熟悉的X86的相关宏定义:1)typedef char * va_list;2)#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) ) 3)#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )4)#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )5)#define va_end(ap) ( ap = (va_list)0 ) 我们逐一看来:第一个我想不必说了,类型定义罢了。第二个是颇有些来头的,我们也不得不搞懂它,因为后面的两个关键的宏定义都用到了。不知道你够不够细心,有没有发现在上面的测试程序中,第二个可变参数明明是char类型,可是在输出结果中占了4个byte。难道所有的参数都会占4个byte的空间?那如果是double类型的参数,且不是会丢失数据!如果你不嫌麻烦的话,再去做个测试吧,在上面的测试程序中用一个double类型(长度为8byte)和一个long double类型(长度为10byte)做可变参数。发现什么?double类型占了8byte,而long double占了12byte。好像都是4的整数倍哦。不得不引出另一个概念了“对齐(alignment)”,所谓对齐,对Intel80x86 机器来说就是要求每个变量的地址都是sizeof(int)的倍数。原来我们搞错了,char类型的参数只占了1byte,但是它后面的参数因为对齐的关系只能跳过3byte存储,而那3byte也就浪费掉了。那为什么要对齐?因为在对齐方式下,CPU 的运行效率要快得多(举个例子吧,要说明的是下面的例子是我从网上摘录下来的,不记得出处了。示例:如下图,当一个long 型数(如图中long1)在内存中的位置正好与内存的字边界对齐时,CPU 存取这个数只需访问一次内存,而当一个long 型数(如图中的long2)在内存中的位置跨越了字边界时,CPU 存取这个数就需要多次访问内存,如i960cx 访问这样的数需读内存三次(一个BYTE、一个SHORT、一个BYTE,由CPU 的微代码执行,对软件透明),所以对齐方式下CPU 的运行效率明显快多了。1 8 16 24 32 - - - -| long1 | long1 | long1 | long1 |- - - -| | | | long2 |- - - -| long2 | long2 | long2 | |- - - -| .)。好像扯得有点远来,但是有助于对_INTSIZEOF(n)的理解。位操作对于我来说是玄的东东。单个位运算还应付得来,而这样一个表达式摆在面前就晕了。怎么办?菜鸟自有菜的办法。(待续)Trackback: /TrackBack.aspx?PostId=14721- C语言中的可变参数函数 CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。第一篇C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:int printf( const char* format, .);它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“”做参数占位符),实际调用时可以有以下的形式:printf(%d,i);printf(%s,s);printf(the number is %d ,string is:%s, i, s); 一个简单的可变参数的C函数 先看例子程序。该函数至少有一个整数参数,其后占位符,表示后面参数的个数不定。在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值。函数代码如下:/示例代码1:可变参数函数的使用#include stdio.h#include stdarg.hvoid simple_va_fun(int start, .) va_list arg_ptr; int nArgValue =start; int nArgCout=0; /可变参数的数目 va_start(arg_ptr,start); /以固定参数的地址为起点确定变参的内存起始地址。 do +nArgCout; printf(the %d th arg: %d,nArgCout,nArgValue); /输出各参数的值 nArgValue = va_arg(arg_ptr,int); /得到下一个可变参数的值 while(nArgValue != -1); return;int main(int argc, char* argv) simple_va_fun(100,-1); simple_va_fun(100,200,-1); return 0;下面解释一下这些代码。从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:由于在程序中将用到以下这些宏:void va_start( va_list arg_ptr, prev_param );type va_arg( va_list arg_ptr, type );void va_end( va_list arg_ptr );va在这里是variable-argument(可变参数)的意思。这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件。函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。然后用va_start宏初始化中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数。然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,在看完这几个宏的内部实现机制后,自然就会明白。第二篇C语言之可变参数问题 C语言中有一种长度不确定的参数,形如:,它主要用在参数个数不确定的函数中,我们最容易想到的例子是printf函数。原型:int printf( const char *format , argument. );使用例:printf(Enjoy yourself everyday!n);printf(The value is %d!n, value);这种可变参数可以说是C语言一个比较难理解的部分,这里会由几个问题引发一些对它的分析。注意:在C+中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。问题:printf的实现请问,如何自己实现printf函数,如何处理其中的可变参数问题? 答案与分析:在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。一个典型实现如下:typedef char* va_list;#define va_start(list) list = (char*)&va_alist#define va_end(list)#define va_arg(list, mode)(mode*) (list += sizeof(mode)-1自己实现printf:#includeint printf(char* format, )va_list ap;va_start(ap, format);int n = vprintf(format, ap);va_end(ap);return n;问题:运行时才确定的参数有没有办法写一个函数,这个函数参数的具体形式可以在运行时才确定?答案与分析:目前没有正规的解决办法,不过独门偏方倒是有一个,因为有一个函数已经给我们做出了这方面的榜样,那就是main(),它的原型是:int main(int argc,char *argv);函数的参数是argc和argv。深入想一下,只能在运行时确定参数形式,也就是说你没办法从声明中看到所接受的参数,也即是参数根本就没有固定的形式。常用的办法是你可以通过定义一个void *类型的参数,用它来指向实际的参数区,然后在函数中根据根据需要任意解释它们的含义。这就是main函数中argv的含义,而argc,则用来表明实际的参数个数,这为我们使用提供了进一步的方便,当然,这个参数不是必需的。虽然参数没有固定形式,但我们必然要在函数中解析参数的意义,因此,理所当然会有一个要求,就是调用者和被调者之间要对参数区内容的格式,大小,有效性等所有方面达成一致,否则南辕北辙各说各话就惨了。问题:可变长参数的传递有时候,需要编写一个函数,将它的可变长参数直接传递给另外的函数,请问,这个要求能否实现?答案与分析:目前,你尚无办法直接做到这一点,但是我们可以迂回前进,首先,我们定义被调用函数的参数为va_list类型,同时在调用函数中将可变长参数列表转换为va_list,这样就可以进行变长参数的传递了。看如下所示:void subfunc (char *fmt, va_list argp).arg = va_arg (fmt, argp); /* 从argp中逐一取出所要的参数 */.void mainfunc (char *fmt, .)va_list argp;va_start (argp, fmt); /* 将可变长参数转换为va_list */subfunc (fmt, argp); /* 将va_list传递给子函数 */va_end (argp);.问题:可变长参数中类型为函数指针我想使用va_arg来提取出可变长参数中类型为函数指针的参数,结果却总是不正确,为什么?答案与分析:这个与va_arg的实现有关。一个简单的、演示版的va_arg实现如下:#define va_arg(argp, type) (*(type *)(argp) += sizeof(type) - sizeof(type)其中,argp的类型是char *。如果你想用va_arg从可变参数列表中提取出函数指针类型的参数,例如int (*)(),则va_arg(argp, int (*)()被扩展为:(*(int (*)() *)(argp) += sizeof (int (*)() -sizeof (int (*)()显然,(int (*)() *)是无意义的。解决这个问题的办法是将函数指针用typedef定义成一个独立的数据类型,例如:typedef int (*funcptr)();这时候再调用va_arg(argp, funcptr)将被扩展为:(* (funcptr *)(argp) += sizeof (funcptr) - sizeof (funcptr)这样就可以通过编译检查了。问题:可变长参数的获取有这样一个具有可变长参数的函数,其中有下列代码用来获取类型为float的实参:va_arg (argp, float);这样做可以吗?答案与分析:不可以。在可变长参数中,应用的是加宽原则。也就是float类型被扩展成double;char, short被扩展成int。因此,如果你要去可变长参数列表中原来为float类型的参数,需要用va_arg(argp, double)。对char和short类型的则用va_arg(argp, int)。问题:定义可变长参数的一个限制为什么我的编译器不允许我定义如下的函数,也就是可变长参数,但是没有任何的固定参数?int f (.).答案与分析:不可以。这是ANSI C 所要求的,你至少得定义一个固定参数。这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值。第一篇C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:int printf( const char* format
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 小学人教部编版对韵歌教学设计
- UPS电源系统采购合同
- 人力资源项目服务外包合作协议2025
- 团员积极分子培训大纲
- 企业劳动合同样本2025
- 个人临时借款合同范本
- 2024-2025学年江苏省南通市如皋市高三(下)适应性英语试卷(二)
- 矿山安全生产管理体系构建考核试卷
- 2024年04月陕西榆林市横山区招聘卫生检验协管员30人笔试历年专业考点(难、易错点)附带答案详解
- 2024年04月河北邯郸市邯山区面向社会招聘核酸检测人员15人笔试历年专业考点(难、易错点)附带答案详解
- 小鹏P7+用户调研报告
- 2025年度矿山买卖中介服务佣金结算协议
- 重庆市建筑安全员C证考试题库
- 绘本故事《小鲤鱼跳龙门》课件
- TCACM 1554-2023 肛漏中医诊疗指南
- 直播间搭建培训
- 刑事诉讼中电子数据冻结的性质及其法律规制
- 2025年重庆三支一扶招募469人高频重点提升(共500题)附带答案详解
- 企业投资项目后评估管理制度
- 健身会籍顾问
- 电力系统分析知到智慧树章节测试课后答案2024年秋东北电力大学
评论
0/150
提交评论