`
xitong
  • 浏览: 6206518 次
文章分类
社区版块
存档分类
最新评论

变长参数函数(转载)

 
阅读更多

变长参数的函数即参数个数可变、参数类型不定的函数。

设计一个参数个数可变、参数类型不定的函数是可能的,最常见的例子是printf函数、scanf函数和高级语言的Format函数。在C/C++中,为了通知编译器函数的参数个数和类型可变(即是不定的、未知的),就必须以三个点结束该函数的声明。

  1. //printf函数的声明
  2. intprintf(constchar*_Format,...);
  3. //scanf函数声明
  4. intscanf(constchar*_Format,...);
  5. //自定义变长参数函数func的声明
  6. intfunc(inta,intb,...);

上面func函数的声明指出该函数至少有两个整型参数和紧随其后的0个或多个类型未知的参数。在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。

  1. //下面这种声明是非法的
  2. intfunc(...);//错误
  3. intfunc(...,inta);//错误


变长参数函数的实现

含有变长参数的函数是怎么实现的呢?变长参数函数的实现其实关键在于怎么使用参数,指定了的参数好说,直接使用指定的参数名称访问,但未指定的参数呢?我们知道函数调用过程中参数传递是通过栈来实现的,一般调用都是从右至左的顺序压参数入栈,因此参数与参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数的内容。对于变长参数函数,结合一定的条件,我们可以根据最后一个指定参数获取之后的省略参数内容。如,对于函数func,我们知道了参数b的地址及类型,就可知道第一个可变参数的栈地址(如果有的话),如果知道第一个可变参数的类型,就可知道第一个可变参数的内容和第二个可变参数的地址(如果有的话)。以此类推,可以实现对可变参数函数的所有参数的访问。

那么,要怎么指定上诉的“一定的条件”呢?最简单的方法就像printf等函数一样,使用格式化占位符。分析格式化字符串参数,通过事先定义好的格式化占位符可知可变参数的类型及个数,从而获取各个参数内容。一般对于可变参数类型相同的函数也可直接在强制参数中指定可变参数的个数和类型,这样也能获取各个参数的内容。

无论哪种,都涉及对栈地址偏移的操作。结合栈存储模式和系统数据类型的字长,我们可根据可变参数的类型很容易得到栈地址的偏移量。这里简单介绍使用va_start、va_arg、va_end三个标准宏来实现栈地址的偏移及获取可变参数内容。这三个宏定义在stdarg.h头文件中,他们可根据预先定义的系统平台自动获取相应平台上各个数据类型的偏移量。

  1. //访问可变参数流程
  2. va_listargs;//定义一个可变参数列表
  3. va_start(args,arg);//初始化args指向强制参数arg的下一个参数;
  4. va_arg(args,type);//获取当前参数内容并将args指向下一个参数
  5. ...//循环获取所有可变参数内容
  6. va_end(args);//释放args

实现一个简单的变长参数函数:

  1. //sum为求和函数,其参数类型都为int,但参数个数不定
  2. //第一个参数(强制参数)n指定后面有多少可变参数
  3. intsum(unsignedintn,...)
  4. {
  5. intsum=0;
  6. va_listargs;
  7. va_start(args,n);
  8. while(n>0)
  9. {
  10. //通过va_arg(args,int)依次获取参数的值
  11. sum+=va_arg(args,int);
  12. n--;
  13. }
  14. va_end(args);
  15. returnsum;
  16. }


对于可变参数函数的调用有一点需要注意,实际的可变参数的个数必须比前面强制参数中指定的个数要多,或者不小于,也即后续参数多一点不要紧,但不能少,如果少了则会访问到函数参数以外的堆栈区域,这可能会把程序搞崩掉。前面强制参数中指定的类型和后面实际参数的类型不匹配也有可能造成程序崩溃。

变长参数函数与默认参数函数

拥有变长参数的函数在声明定义时其参数个数与类型是不定的,在运行调用时参数的状态则是一定的。而默认参数函数在声明定义时其参数类型与个数都是一定的,只是后面部分参数指定了默认值,可通过省略(不指定)部分参数调用这个默认参数函数。但是默认参数函数还是使用了声明中指定的全部参数,只不过编译器做了个顺水人情,自动给后部分参数赋了默认值;而变长参数函数则仅仅使用了运行调用时提供的参数。

分享到:
评论

相关推荐

    QT 绘图函数

    函数drawPie()的最后两个参数值的单位为一度的十六分之一。 下面的代码是图8.5(c)中绘制贝赛尔曲线的代码: QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QPainterPath path; ...

    windows用户称拦截api

    Windows用户层下拦截api的原理与实现(附源码) (2008-03-29 16:15:07)转载▼ 标签: computer 杂谈 声明:本页所发布的技术文章及其附件,供自由技术传播,拒绝商业使用。本页文章及其附件的所有权归属本文作者...

    遗传算法优化小波神经网络.zip

    其中,BP神经网络结构确定部分根据拟合函数输入输出参数个数确定,进而确定遗传算法个体的长度。遗传算法优化使用遗传算法来优化BP神经网络的权值和阈值,种群中的每个个体都包含了一个网络所有权值和阈值,个体通过...

    人工智能AI、机器学习模型理解.pdf

    模型就是通过当前数据集得到⼀个复杂的多维函数,可以理解为 y = w1.x1+w2.x2+...+b 只是这个函数极为复杂,他的参数是要随之改 变,⽽y就是我们的⽬标值,这个整体我们可以理解为⼀个策略或者⼀个函数,我们要做的...

    Qt Creator 的安装和hello world 程序+其他程序的编写--不是一般的好

    句,这里我们将其放到private 里,因为一般的函数都放在public 里,而变量 都放在private 里。 #ifndef WIDGET_H #define WIDGET_H #include #include "mydlg.h" //包含头文件 namespace Ui { class Widget; } ...

    c语言数据结构字符串模式匹配算法.zip

    好在有现成的函数,当初发明KMP算法,写出这个函数的先辈,令我佩服得六体投地。我等后生小子,理解起来,都要反复琢磨。下面是这个函数: void get_nextval(const char *T, int next[]) { // 求模式串T的next函...

    java设计模式CatalogDAOFactory是典型的工厂方法

    但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。 为什么说代码很难看,初学者可能没有这种感觉,我们...

    二十三种设计模式【PDF版】

    新手需要花费较长时间领会良好的面向对象设计是怎么回事。有经验的设计者显然知道一些新手所不知道的东西,这又 是什么呢? 内行的设计者知道:不是解决任何问题都要从头做起。他们更愿意复用以前使用过的解决方案...

    arcgis工具

    转载ESRI论坛Lucy1114帖子说明: 12. 导出Shape格式为其他软件识别的打印格式如JEPG等格式 FILE/EXPORT MAP 然后选择相应的图片格式,此时也可设置答应的分辨率 pdi 13. 建立注记层 方法一.carvert to ...

    asp.net知识库

    从NUnit中理解.NET自定义属性的应用(转载) 如何在.NET中实现脚本引擎 (CodeDom篇) .NET的插件机制的简单实现 我对J2EE和.NET的一点理解 难分难舍的DSO(一) InternalsVisibleToAttribute,友元程序集访问属性 ...

    Hibernate注释大全收藏

    Hibernate注释大全收藏 声明实体Bean @Entity public class Flight implements Serializable { Long id; @Id public Long getId() { return id; } public void setId(Long id) { this.id = id;...

Global site tag (gtag.js) - Google Analytics