c++ 可变参数列表,c语言可变参数函数

  c++ 可变参数列表,c语言可变参数函数

  C/C可变参数函数(转载)-ZJHFQQ-52RD Blog _ 52RD . com专栏

  C/C变参数函数(再版)(2007-4-1915:57)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)

  {

  返回(a b);

  }

  函数调用:

  添加(1,2);

  //汇编代码是:

  推2;参数堆叠

  推1;参数堆叠

  调用@ Add调用函数。其实还有编译器用来定位函数的表达式,这里省略。

  加esp,8;调用者负责清除堆栈

  如果调用函数时使用的调用协议与函数原型中声明的不一致,将导致堆栈错误。这是另一个话题,这里就不赘述了。

  此外,c/c编译器支持宏形式的可变参数函数。这些宏包括va_start、va_arg和va_end等。这样做的原因是为了增加程序的可移植性。屏蔽硬件平台不同造成的差异。

  所有支持可变参数函数的宏都在stdarg.h和varargs.h中定义。例如,在标准ANSI表单中,这些宏定义为:

  typedef char * va _ list//字符串指针

  # define _ int sizeof(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=_ int sizeof(t))-_ int sizeof(t)))

  # define va _ end(AP)(AP=(va _ list)0)

  Macro _INTSIZEOF是用来按照整数字节对齐指针的,因为在C调用协议下,所有的参数都是以整数字节(指针或者值)来堆叠的。

  第三,如何定义这种函数。

  可变参数函数在不同的系统中有不同的定义形式。

  1.使用ANSI标准形式时,参数数量可变的函数的原型声明为:

  type funcname(type para1,type para2,…);

  这个定义有三点需要解释:

  一般来说,这种形式至少需要一个普通的形参,可变形参由三个“.”定义。所以“……”不是省略的意思,而是函数原型的一部分。Type是函数返回值和形参的类型。

  例如:

  int MyPrintf(char const* fmt,…);

  然而,我们也可以这样定义函数:

  void my func(…);

  但是,在这种情况下,我们不能使用函数的参数,因为我们不能通过上面提到的宏提取每个参数。所以除非你的函数代码不使用参数表中的任何参数,否则你必须在参数表中至少使用一个公共参数。

  注意变量参数只能在函数参数表的末尾。你不能:

  void MyFunc(…,int I);

  2.在UNIX兼容系统下采用声明方式时,参数个数可变的函数的原型为:

  type funcname(va _ list);

  但是,当需要实现函数时,必须在函数名后面加上va_dcl。例如:

  #包括

  int average(va _ list);

  无效总管(无效)

  {。//代码

  }

  /* UNIX兼容格式*/

  中间平均值(列表)

  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===================

  #包含stdio.h

  #包含字符串. h

  #包含stdarg.h

  /*函数原型声明需要至少一个明确的参数。注意括号中的省略号*/

  int demo( char *,…);

  无效总管(无效)

  {

  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)/*使用空字符串表示参数输入的结束*/

  打破;

  Printf("参数#%d是:% s \ n ",argno,para);

  argno//注意:栈底在高位地址,栈顶在低位地址,所以这里是

  }

  va _ end(argp);/*将argp设置为NULL */

  返回0;

  }

  //输出结果

  参数#0是:这

  参数#1是:是

  参数#2是

  参数#3是:演示!

  请注意,上面的示例没有使用第一个参数,下面的示例将使用所有参数。

  //=====================示例程序2===============

  #包括

  #包括

  int average( int first,…);//输入若干整数,并求出它们的平均值

  无效总管(无效)

  {

  /*调用3个整数(-1表示结束)*/

  printf("平均为:%d\n ",average(2,3,4,-1));

  /*调用4个整数*/

  printf("平均为:%d\n ",average(5,7,9,11,-1));

  /*只有终结者的召唤*/

  printf("平均值为:%d\n ",平均值(-1));

  }

  /*返回几个整数平均值的函数*/

  int average( int first,…)

  {

  int count=0,sum=0,i=first

  va_list标记;

  va_start( marker,first);//初始化

  而(我!=-1 )

  {

  sum=I;//首先添加第一个参数

  数数;

  i=va_arg( marker,int);//接受下一个参数

  }

  va_end(标记);

  返回(总和?(sum/count):0);

  }

  //输出结果

  平均值为:3

  平均值为:8

  平均值为:0

  第五,关于可变参数的传递。

  有人问这个问题,如果我定义了一个变参数函数,在这个函数内部调用其他变参数函数,怎么传递参数?在上面的例子中,使用宏va_arg来逐个提取参数。你能把它们传递给另一个函数而不提取它们吗?

  让我们先来看看printf的实现:

  int _ _ cdecl printf(const char * format,…)

  {

  va _ list arglist

  int打磨;

  int retval

  va_start(arglist,format);//arglist指向format后的第一个参数。//不关心其他代码

  retval=_output(stdout,format,arglist);//将格式格式和参数传递给输出函数。//不关心其他代码

  返回(retval);

  }

  我们先模仿这个函数写一个:

  #包括

  #包括

  int mywrite(char *fmt,…)

  {

  va _ list arglist

  va_start(arglist,fmt);

  返回printf(fmt,arglist);

  }

  void main()

  {

  int i=10,j=20

  char buf[]="这是一个测试";

  双f=12.345

  mywrite("String: %s\nInt: %d,%d\nFloat :%4.2f\n ",buf,I,j,f);

  }

  让我们来看看。哈,有很多错误。仔细分析原因。根据宏的定义,我们知道arglist是一个指针,指向第一个变量参数,但是所有的参数都位于堆栈中,所以arglist指向堆栈中的某个位置。通过arglist的值,我们可以直接查看堆栈中的内容:

  arglist-指向堆栈内部,包括

  067 FD 78 E0 FD 67 00//指向字符串“这是一个测试”

  067 FD 7 c0a 00 00 00//整数I的值

  067 FD 80 14 00 00 00//整数j的值。

  067 FD 84 713d 0a 7//双变量F,占用8个字节

  0067FD88 A3 B0 28 40

  0067FD8C 00 00 00 00

  如果直接调用printf(fmt,arglist);只需将arglist指针0067FD78的值放入堆栈,然后将格式字符串放入堆栈,相当于调用:

  printf(fmt,0067 FD 78);

  如此自然的调用必然会出错。

  能不能把参数一个一个提取出来传递给其他函数?考虑一下一次性传入所有参数的问题。

  如果调用系统库函数,这是不可能的。因为参数提取处于运行状态,而参数堆栈是在编译时确定的。如果编译器不能预测运行状态,给出正确的参数堆栈代码。虽然我们可以提取运行状态中的每个参数,但我们不能一次堆栈所有参数,即使用汇编代码也很难实现,因为这不仅可以通过简单的推代码来完成。

  如果接受参数的函数也是我们自己写的,自然可以把arglist指针放入堆栈,然后在函数中自己分析arglist指针中的参数,一个一个提取出来进行处理。但这样做似乎没有任何意义。一方面,这个函数不需要做成变参数函数。另一方面,直接分析第一个函数中的参数,然后进行处理,不是更简单吗?

  我们唯一能做的就是一个一个的解析参数,然后在循环中调用其他变量参数函数,一次传递一个参数。这里还有一个问题,就是参数表中不可变参数的传递。有些情况下是不能简单转移的。以上面的例子为例。通常,当我们解析参数时,我们还需要解析格式字符串:

  #包括

  #包括

  #包括

  //测试这个,开个玩笑

  空t(…)

  {

  printf(" \ n ");

  }

  int mywrite(char *fmt,…)

  {

  va _ list arglist

  va_start(arglist,fmt);

  炭化温度[255];

  strcpy(temp,fmt);//复制格式字符串

  字符格式[255];

  char *p=strchr(temp, % );

  int I=0;

  int iParam

  双fParam

  而(p!=空)

  {

  while((*p a *p z) (*p!=0))p;

  if(* p==0)break;

  p;

  //格式化字符串

  int nChar=p-temp;

  strncpy(Format,temp,nChar);

  format[nChar]=0;

  //参数

  if(Format[nChar-1]!=f )

  {

  iParam=va_arg( arglist,int);

  printf(格式,iParam);

  }

  其他

  {

  fParam=va_arg( arglist,double);

  printf(格式,fParam);

  }

  我;

  if(* p==0)break;

  strcpy(temp,p);

  p=strchr(temp, % );

  }

  if(temp[0]!=0)

  printf(temp);

  返回I;

  }

  void main()

  {

  int i=10,j=20

  char buf[]="这是一个测试";

  双f=123.456

  mywrite("String: %s\nInt: %d,%d\nFloat :%4.2f\nEnd ",buf,I,j,f,0);

  t(“AAA”,I);

  }

  //输出:

  字符串:这是一个测试

  整数:10,20

  浮点数:123.46

  目标

  当然,这里的分析是不完善的。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • 详解c语言中的字符串数组是什么,详解c语言中的字符串数组结构,详解C语言中的字符串数组
  • 表达式求值c++实现,c语言实现表达式求值
  • 看懂c语言基本语法,C语言详解,C语言的基本语法详解
  • 用c语言实现快速排序算法,排序算法设计与实现快速排序C语言,C语言实现快速排序算法实例
  • 深入解析c语言中函数指针的定义与使用方法,深入解析c语言中函数指针的定义与使用情况,深入解析C语言中函数指针的定义与使用
  • 描述E-R图,E-R图举例,关于C语言中E-R图的详解
  • 折半查找法C语言,折半查找算法(算法设计题)
  • 折半查找法C语言,c语言折半法查找数据,C语言实现折半查找法(二分法)
  • 扫雷小游戏c++代码设计,c语言扫雷游戏源代码,C语言实现扫雷小游戏详细代码
  • 怎样统计程序代码行数,C语言统计行数,C#程序员统计自己的代码行数
  • 基于c语言的贪吃蛇游戏程序设计,用c语言编写贪吃蛇游戏程序,C语言实现简单的贪吃蛇游戏
  • 图的两种遍历算法,图的遍历算法代码c语言,Python算法之图的遍历
  • 留言与评论(共有 条评论)
       
    验证码: