




已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
如何实现参数个数可变的函数参数个数不定的函数,最频繁使用的就是printf()与scanf()。其实,我们也可以自己实现这样的功能,首先看一个例子:#include #include int Sum(int first, int second, .) int sum = 0, t = first; va_list vl; va_start(vl, first); while (t != -1) sum += t; t = va_arg(vl, int);/将当前参数转换为int类型 va_end(vl); return sum;int main(int argc, char* argv) printf(The sum is %dn, Sum(30, 20, 10, -1);/-1是参数结束标志 return 0;在上面的例子中,实现了一个参数个数不定的求int型和的函数Sum()。函数中一开始定义了一个va_list型变量vl,该变量用来访问可变参数,实际上就是指针,接着使用va_start使vl指向第一个参数,然后再使用va_arg来遍历每一个参数,va_arg返回参数列表中的当前参数并使vl指向参数列表中的下一个参数。最后通过va_end把vl指针清为NULL。在这里,va_start,va_arg,va_end其实都是宏,那么这些宏又是如何实现他们的功能的呢?一 个很显然的问题就是既然参数个数在函数调用之前没有确定,所以,在函数定义的时候没有办法像普通函数那样使用确定的变量来接受传进来的参数,于是,问题的 关键就是如何接收到这些不确定的参数了?首先,看看函数调用时实际发生的情况,在函数调用的时候,使用栈传递参数,拿上例来说,如果调用Sum(30, 20, 10, -1),那么参数入栈后的情形如下图所示(假定按照自右至左的顺序入栈):既然参数在栈中的情形已经知道了,那么,如果使用指针(程序中的vl)指向第一个参数(va_start(vl, first)),因为所有参数都是连续存放的,通过移动指针就可以访问到每一个参数了(va_arg(vl, int)),这就是我在程序中使用到那几个宏的实现思想。 明白了以上原理之后,就可以完全不使用宏,自己实现这样的一个功能了,下面的程序是我按照上面的思想改写的:#include int Sum(int first, int second, .)int sum = 0, t = first;char * vl;/定义一个指针vl = (char *)&first;/使指针指向第一个参数while (*vl != -1)/-1是预先给定的结束符 sum += *(int *)vl;/类型转换 vl += sizeof(int);/移动指针,使指针指向下一个参数return sum;int main(int argc, char* argv)printf(The sum is %dn, Sum(30, 20, 10, -1);/-1是参数结束标志return 0;可以看出,通过使用指针的确实现了参数个数不定的函数了,但是程序中还有一个问题,那就是移动指针,在程序中因为我使用的参数都是相同的int类型,所以可以事先知道访问下一个参数的时候应该移动sizeof(int)个字节,但是,如果参数的类型不同呢?这的确是个比较麻烦的问题,因为不同的数据类型占用的字节数可能是不一样的(如double型为8个字符,short int型为2个),所以很难事先确定应该移动多少个字节!但是办法还是有的,这就是使用指针了,无论什么类型的指针,都是占用4个字节,所以,可以把所有的传人参数都设置为指针,这样一来,就可以通过移动固定的4个字节来实现遍历可变参数的目的了,至于如果取得指针中的内容并使用他们,当然也是无法预先得知的了,所以这大概也就是像printf(),scanf()之类的函数还需要一个格式控制符的原因吧_!不过实现起来还是有不少麻烦,暂且盗用vprintf()来实现一个与printf()函数一样功能的函数了,代码如下:void myPrint(const char *frm, .) va_list vl; va_start(vl, frm); vprintf(frm, vl); va_end(vl);如何用C实现参数个数可变的函数采用C语言编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可 以根据需要确定。典型的例子有大家熟悉的函数printf()、scanf()和系统调用execl()等。那么它们是怎样实现的呢? C编译器通常提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括vastart、vaarg和vaend等。 使用这些宏有两种不同的形式,二者在程序中包括的头文件不同,宏的定义也存在一些差别。这两种方式对应的头文件和宏的声明见表1。 采用ANSI标准形式时,参数个数可变的函数的原型声明是: type funcname(type para1, type para2, .) 这种形式至少需要一个普通的形式参数,后面的省略号不表示省略,而是函数原型的一部分,type是函数返回值和形式参数的类型。 采用与UNIX System V兼容的声明方式时,参数个数可变的函数原型是: type funcname(vaalist) vadcl 这种形式不需要提供任何普通的形式参数,type是函数返回值的类型。vadcl是对函数原型声明中参数vaalist的详细声明,实际是一个宏 定义,对不同的硬件平台采用不同的类型来定义,但在最后都包括了一个分号,因此vadcl后不再需要加上分号了。vadcl在代码中必须原样给 出,vaalist在VC中可以原样给出,也可以略去,但在UNIX上的CC或Linux上的GCC中都要省略掉。此外,采用头文件stdarg.h编 写的程序是符合ANSI标准的,可以在各种操作系统和硬件上运行,而采用头文件varargs.h的方式仅仅是为了与以前的程序兼容。所以建议大家使用前 者。两种方式的基本原理是一致的,只是在语法形式上有一些细微的区别。以下主要就前一种方式对参数的处理做出说明。 vastart使argp指向第一个可选参数。vaarg返回参数列表中的当前参数并使argp指向参数列表中的下一个参数。vaend把argp指针清为NULL。函数体内可以多次遍历这些参数,但是都必须以vastart开始,并以vaend结尾。 调用者在实际调用参数个数可变的函数时,要通过一定的方法指明实际参数的个数,例如把最后一个参数置为空字符串(系统调用execl()就是这样的)、1或其他的方式。 下面给出一个具体的例子,前一部分是采用了符合ANSI标准形式的代码,后一部分是采用了与UNIX System V兼容方式的代码。代码中加了一些注释,这里就不再解释了。该例子已经在VC/Windows NT4.0、CC/AIX4.3.2.0、GCC/Redhat Linux 6.0环境下编译并正常运行。 1.演示如何使用参数个数可变的函数,采用ANSI标准形式 include stdio.h include string.h include stdarg.h / 函数原型声明,至少需要一个确定的参数,注意括号内的省略号 / int demo( char , . ); void main( void ) demo(DEMO, This, is, a, demo!, ); / ANSI标准形式的声明方式,括号内的省略号表示可选参数 / int demo( char msg, . ) valist argp; / 定义保存函数参数的结构 / int argno = 0; / 纪录参数个数 / char para; / 存放取出的字符串参数 / / argp指向传入的第一个可选参数,msg是最后一个确定的参数 / vastart( argp, msg ); while (1) para = vaarg( argp, char ); / 取出当前的参数,类型为char . / if ( strcmp( para, ) = 0 ) / 采用空串指示参数输入结束 / break; printf(Parameter d is: sn, argno, para); argno; va_end( argp ); / 将argp置为NULL / return 0; 2.演示如何使用参数个数可变的函数,采用与UNIX System V兼容的方式 include stdio.h include string.h include varargs.h / 函数原型声明,括号内的类型valist在VC/Windows NT4.0可以保留,但在AIX和Linux下需要去掉,即改成int demo( ) / int demo( valist ); void main( void ) demo(This, is, a, demo!, ); / UNIX System V采用的声明方式,括号内是vaalist,不是valist,而且vadcl后面不需要分号 / int demo( vaalist ) vadcl valist argp; / 定义保存函数参数的结构 / int argno = 0; / 纪录参数个数 / char para; / 存放取出的字符串参数 / vastart( argp ); / argp指向第一个可选参数 / while (1) para = vaarg( argp, char ); / 取出当前的参数,类型为char / if ( strcmp( para, ) = 0 ) / 采用空串指示参数输入结束 / break; printf(Parameter d is: sn, argno, para); argno; va_end( argp ); / 将argp置为NULL / return 0; c/c+支持可变参数的函数一、为什么要使用可变参数的函数? 一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需 要确定,因此c语言引入可变参数函数。这也是c功能强大的一个方面,其它某些语言,比如fortran就没有这个功能。 典型的可变参数函数的例子有大家熟悉的printf()、scanf()等。 二、c/c+如何实现可变参数的函数? 为了支持可变参数函数,C语言引入新的调用协议, 即C语言调用约定 _cdecl . 采用C/C+语言编程的时候,默认使用这个调用约定。如果要采用其它调用约定,必须添加其它关键字声明,例如WIN32 API使用PASCAL调用约定,函数名字之前必须加_stdcall关键字。 采用C调用约定时,函数的参数是从右到左入栈,个数可变。由于函数体不能预先知道传进来的参数个数,因此采用本约定时必须由函数调用者负责堆栈清理。举个例子:/C调用约定函数 int _cdecl Add(int a, int b) return (a + b); 函数调用: Add(1, 2); /汇编代码是: push 2 ;参数b入栈 push 1 ;参数a入栈 call Add ;调用函数。其实还有编译器用于定位函数的表达式这里把它省略了 add esp,8 ;调用者负责清栈 如果调用函数的时候使用的调用协议和函数原型中声明的不一致,就会导致栈错误,这是另外一个话题,这里不再细说。 支持可变参数函数的所有宏都定义在stdarg.h 和 varargs.h中。例如标准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调用协议下面,参数入栈都是整数字节(指针或者值)。 三、如何定义这类的函数。 可变参数函数在不同的系统下,采用不同的形式定义。 1、用ANSI标准形式时,参数个数可变的函数的原型声明是: type funcname(type para1, type para2, ); 关于这个定义,有三点需要说明: 一般来说,这种形式至少需要一个普通的形式参数,可变参数就是通过三个.来定义的。所以不表示省略,而是函数原型的一部分。type是函数返回值和形式参数的类型。 例如: int MyPrintf(char const* fmt, ); 但是,我们也可以这样定义函数: void MyFunc(); 但是,这样的话,我们就无法使用函数的参数了,因为无法通过上面所讲的宏来提取每个参数。所以除非你的函数代码中的确没有用到参数表中的任何参数,否则必须在参数表中使用至少一个普通参数。 注意,可变参数只能位于函数参数表的最后。不能这样: void MyFunc(, int i); 2、采用与UNIX 兼容系统下的声明方式时,参数个数可变的函数原型是: type funcname(va_alist); 但是要求函数实现的时候,函数名字后面必须加上va_dcl.例如:i nclude int average( va_list );void main( void ) 。/代码/* UNIX兼容形式*/int average( va_alist )va_dcl 。/代码 这种形式不需要提供任何普通的形式参数。type是函数返回值的类型。va_dcl是对函数原型声明中参数va_alist的详细声明,实际是一个宏定义。根据平台的不同,va_dcl的定义稍有不同。 在varargs.h中,va_dcl的定义后面已经包括了一个分号。因此函数实现的时候,va_dcl后不再需要加上分号了。 3、采用头文件stdarg.h编写的程序是符合ANSI标准的,可以在各种操作系统和硬件上运行;而采用头文件varargs.h的方式仅仅是为了与以 前的程序兼容,两种方式的基本原理是一致的,只是在语法形式上有一些细微的区别。 所以一般编程的时候使用stdarg.h.下面的所有例子代码都采用ANSI标准格式。四、可变参数函数的基本使用方法 下面通过若干例子,说明如何实现可变参数函数的定义和调用。/= 例子程序1 =i nclude i nclude i nclude /* 函数原型声明,至少需要一个确定的参数,注意括号内的省略号 */int demo( char *, . );void main( void )demo(DEMO, This, is, a, demo!, 0);int demo( char *msg, . )va_list argp; /* 定义保存函数参数的结构 */ int argno = 0; /* 纪录参数个数 */ char *para; /* 存放取出的字符串参数 */ 使用宏va_start, 使argp指向传入的第一个可选参数,/ 注意 msg是参数表中最后一个确定的参数,并非参数表中第一个参数va_start( argp, msg ); while (1)/取出当前的参数,类型为char */如果不给出正确的类型,将得到错误的参数para = va_arg( argp, char *); if ( strcmp( para, 0) = 0 ) /* 采用空串指示参数输入结束 */ break;printf(参数 #%d 是: %sn, argno, para); argno+; va_end( argp ); /* 将argp置为NULL */ return 0;/输出结果参数 #0 是: This参数 #1 是: is参数 #2 是: a参数 #3 是: demo!注意到上面的例子没有使用第一个参数,下面的例子将使用所有参数/= 例子程序2 =i nclude i nclude int average( int first, . ); /输入若干整数,求它们的平均值void main( void ) /* 调用3个整数(-1表示结尾) */ printf( Average is: %dn, average(2,3,4, -1); /*调用4个整数*/ printf( Average is: %dn, average(5,7,9, 11,-1); /*只有结束符的调用*/ printf( Average is: %dn, average(-1) );/* 返回若干整数平均值的函数 */int average( int first, . ) int count = 0, sum = 0, i = first; va_list marker; va_start( marker, first ); /初始化 while( i != -1 ) sum += i; /先加第一个参数 count+; i = va_arg( marker, int);/取下一个参数 va_end( marker ); return( sum ? (sum / count) : 0 );/输出结果Average is: 3Average is: 8Average is: 0 五、关于可变参数的传递问题 有人问到这个问题,假如我定义了一个可变参数函数,在这个函数内部又要调用其它可变参数函数,那么如何传递参数呢?上面的例子都是使用宏va_arg逐个把参数提取出来使用,能否不提取,直接把它们传递给另外的函数呢? 我们先看printf的实现:int _cdecl printf (const char *format, .) va_list arglist; int buffing; int retval; va_start(arglist, format); /arglist指向format后面的第一个参数./不关心其它代码 retval = _output(stdout,format,arglist); /把format格式和参数传递给output函数./不关心其它代码 return(retval); 我们先模仿这个函数写一个:i nclude i
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 子宫肌瘤中西医课件
- 年前安全培训物业课件
- 2025年青州市事业单位考试真题
- 工业安全锁培训资料课件
- 平面设计盛宴课件
- 姓氏歌课件刘碧珍
- Fmoc-pro-osu-N-Fmoc-L-proline-N-succinimidyl-ester-生命科学试剂-MCE
- 工业安全培训平台课件
- 北京市第十八中学招聘教师笔试真题2024
- 农发行乌兰察布市兴和县2025秋招无领导模拟题角色攻略
- 济宁市“技能状元”职业技能竞赛-全市煤化工行业技能大赛化学检验工参考题库
- 邢台城市介绍课件
- 怎样写好硬笔字-硬笔书法教程课件 4-1 硬笔隶书笔画技法
- 旅行社旅游突发公共事件应急预案
- 统编版中考语文一轮复习:义务教育语文课程常用字表(3500字注音版)(2022版课标)
- 建筑工程技术专业《房屋建筑学》课程标准
- 人教版部编版统编版一年级语文上册汉语拼音5《gkh》课件
- DL-T1083-2019火力发电厂分散控制系统技术条件
- 《2024年北京市医疗服务收费目录》
- 意外险医疗险重疾险
- 便利店陈列培训
评论
0/150
提交评论