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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。