C++ 参数个数不确定.docx

上传人:b****8 文档编号:30680661 上传时间:2023-08-19 格式:DOCX 页数:10 大小:19.51KB
下载 相关 举报
C++ 参数个数不确定.docx_第1页
第1页 / 共10页
C++ 参数个数不确定.docx_第2页
第2页 / 共10页
C++ 参数个数不确定.docx_第3页
第3页 / 共10页
C++ 参数个数不确定.docx_第4页
第4页 / 共10页
C++ 参数个数不确定.docx_第5页
第5页 / 共10页
点击查看更多>>
下载资源
资源描述

C++ 参数个数不确定.docx

《C++ 参数个数不确定.docx》由会员分享,可在线阅读,更多相关《C++ 参数个数不确定.docx(10页珍藏版)》请在冰豆网上搜索。

C++ 参数个数不确定.docx

C++参数个数不确定

c/c++支持可变参数的函数,即函数的参数是不确定的

 

名人名言:

个人如果但靠自己,如果置身于集体的关系之外,置身于任何团结民众的伟大思想的范围之外,就会变成怠惰的、保守的、与生活发展相敌对的人。

——高尔基

c/c++支持可变参数的函数,即函数的参数是不确定的。

一、为什么要使用可变参数的函数?

一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。

但在某些情况下希望函数的参数个数可以根据需要确定,因此c语言引入可变参数函数。

这也是c功能强大的一个方面,其它某些语言,比如fortran就没有这个功能。

典型的可变参数函数的例子有大家熟悉的printf()、scanf()等。

二、c/c++如何实现可变参数的函数?

为了支持可变参数函数,C语言引入新的调用协议,即C语言调用约定__cdecl。

采用C/C++语言编程的时候,默认使用这个调用约定。

如果要采用其它调用约定,必须添加其它关键字声明,例如WIN32API使用PASCAL调用约定,函数名字之前必须加__stdcall关键字。

采用C调用约定时,函数的参数是从右到左入栈,个数可变。

由于函数体不能预先知道传进来的参数个数,因此采用本约定时必须由函数调用者负责堆栈清理。

举个例子:

//C调用约定函数

int__cdeclAdd(inta,intb)

{

return(a+b);

}

函数调用:

Add(1,2);

//汇编代码是:

push2;参数b入栈

push1;参数a入栈

call@Add;调用函数。

其实还有编译器用于定位函数的表达式这里把它省略了

addesp,8;调用者负责清栈

如果调用函数的时候使用的调用协议和函数原型中声明的不一致,就会导致栈错误,这是另外一个话题,这里不再细说。

另外c/c++编译器采用宏的形式支持可变参数函数。

这些宏包括va_start、va_arg和va_end等。

之所以这么做,是为了增加程序的可移植性。

屏蔽不同的硬件平台造成的差异。

支持可变参数函数的所有宏都定义在stdarg.h和varargs.h中。

例如标准ANSI形式下,这些宏的定义是:

typedefchar*va_list;//字符串指针

#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

#defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))

#defineva_arg(ap,t)(*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

#defineva_end(ap)(ap=(va_list)0)

使用宏_INTSIZEOF是为了按照整数字节对齐指针,因为c调用协议下面,参数入栈都是整数字节(指针或者值)。

三、如何定义这类的函数。

可变参数函数在不同的系统下,采用不同的形式定义。

1、用ANSI标准形式时,参数个数可变的函数的原型声明是:

typefuncname(typepara1,typepara2,…);

关于这个定义,有三点需要说明:

一般来说,这种形式至少需要一个普通的形式参数,可变参数就是通过三个’."来定义的。

所以”…”不表示省略,而是函数原型的一部分。

type是函数返回值和形式参数的类型。

例如:

intMyPrintf(charconst*fmt,…);

但是,我们也可以这样定义函数:

voidMyFunc(…);

但是,这样的话,我们就无法使用函数的参数了,因为无法通过上面所讲的宏来提取每个参数。

所以除非你的函数代码中的确没有用到参数表中的任何参数,否则必须在参数表中使用至少一个普通参数。

注意,可变参数只能位于函数参数表的最后。

不能这样:

voidMyFunc(…,inti);

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

typefuncname(va_alist);

但是要求函数实现的时候,函数名字后面必须加上va_dcl。

例如:

#include

intaverage(va_list);

voidmain(void)

{

//代码

}

/*UNIX兼容形式*/

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

#include

#include

#include

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

intdemo(char*,…);

voidmain(void)

{

demo(”DEMO”,“This”,“is”,“a”,“demo!

”,“\0″);

}

intdemo(char*msg,…)

{

va_listargp;/*定义保存函数参数的结构*/

intargno=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是:

%s\n”,argno,para);

argno++;//注意:

栈底在高地址,栈顶在低地址,所以这里是++

}

va_end(argp);/*将argp置为NULL*/

return0;

}

//输出结果

参数#0是:

This

参数#1是:

is

参数#2是:

a

参数#3是:

demo!

注意到上面的例子没有使用第一个参数,下面的例子将使用所有参数

//================================例子程序2===============

#include

#include

intaverage(intfirst,…);//输入若干整数,求它们的平均值

voidmain(void)

{

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

printf(“Averageis:

%d\n”,average(2,3,4,-1));

/*调用4个整数*/

printf(“Averageis:

%d\n”,average(5,7,9,11,-1));

/*只有结束符的调用*/

printf(“Averageis:

%d\n”,average(-1));

}

/*返回若干整数平均值的函数*/

intaverage(intfirst,…)

{

intcount=0,sum=0,i=first;

va_listmarker;

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

while(i!

=-1)

{

sum+=i;//先加第一个参数

count++;

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

}

va_end(marker);

return(sum?

(sum/count):

0);

}

//输出结果

Averageis:

3

Averageis:

8

Averageis:

0

五、关于可变参数的传递问题

有人问到这个问题,假如我定义了一个可变参数函数,在这个函数内部又要调用其它可变参数函数,那么如何传递参数呢?

上面的例子都是使用宏va_arg逐个把参数提取出来使用,能否不提取,直接把它们传递给另外的函数呢?

我们先看printf的实现:

int__cdeclprintf(constchar*format,…)

{

va_listarglist;

intbuffing;

intretval;

va_start(arglist,format);//arglist指向format后面的第一个参数

//不关心其它代码

retval=_output(stdout,format,arglist);//把format格式和参数传递给output函数

//不关心其它代码

return(retval);

}

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

#include

#include

intmywrite(char*fmt,…)

{

va_listarglist;

va_start(arglist,fmt);

returnprintf(fmt,arglist);

}

voidmain()

{

inti=10,j=20;

charbuf[]=“Thisisatest”;

doublef=12.345;

mywrite(”String:

%s\nInt:

%d,%d\nFloat:

%4.2f\n”,buf,i,j,f);

}

运行一下看看,哈,错误百出。

仔细分析原因,根据宏的定义我们知道arglist是一个指针,它指向第一个可变的参数,但是所有的参数都位于栈中,所以arglist指向栈中某个位置,通过arglist的值,我们可以直接查看栈里面的内容:

arglist->指向栈里面,内容包括

0067FD78E0FD6700//指向字符串”Thisisatest”

0067FD7C0A000000//整数i的值

0067FD8014000000//整数j的值

0067FD84713D0AD7//double变量f,占用8个字节

0067FD88A3B02840

0067FD8C00000000

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

printf(fmt,0067FD78);

自然这样的调用肯定会出现错误。

我们能不能逐个把参数提取出来,再传递给其它函数呢?

先考虑一次性把所有参数传递进去的问题。

如果调用的是系统库函数,这种情况下是不可能的。

因为提取参数是在运行态,而参数入栈是在编译的时候确定的。

无法让编译器预知运行态的事情给出正确的参数入栈代码。

而我们在运行态虽然可以提取每个参数,但是无法将参数一次性全部压栈,即使使用汇编代码实现起来也是很困难的,因为不单是一个简单的push代码就可以做到。

如果接受参数的函数也是我们自己写的,自然我们可以把arglist指针入栈,然后在函数中自己解析arglist指针里面的参数,逐个提取出来处理。

但是这样做似乎没有什么意义,一方面,这个函数没有必要也做成可变参数函数,另一方面直接在第一个函数中解析参数,然后处理不是更简单么?

我们唯一可以做到的是,逐个解析参数,然后循环中调用其它可变参数函数,每次传递一个参数。

这里又有一个问题,就是参数表中的不可变参数的传递问题,有些情况下不能简单的传递,以上面的例子为例,通常我们解析参数的同时,还需要解析格式字符串:

#include

#include

#include

//测试一下这个,开个玩笑

voidt(…)

{

printf(”\n”);

}

intmywrite(char*fmt,…)

{

va_listarglist;

va_start(arglist,fmt);

chartemp[255];

strcpy(temp,fmt);//CopytheFormatstring

charFormat[255];

char*p=strchr(temp,’%");

inti=0;

intiParam;

doublefParam;

while(p!

=NULL)

{

while((*p<"a"||*p>‘z’)&&(*p!

=0))p++;

if(*p==0)break;

p++;

//格式字符串

intnChar=p-temp;

strncpy(Format,temp,nChar);

Format[nChar]=0;

//参数

if(Format[nChar-1]!

=‘f’)

{

iParam=va_arg(arglist,int);

printf(Format,iParam);

}

else

{

fParam=va_arg(arglist,double);

printf(Format,fParam);

}

i++;

if(*p==0)break;

strcpy(temp,p);

p=strchr(temp,‘%’);

}

if(temp[0]!

=0)

printf(temp);

returni;

}

voidmain()

{

inti=10,j=20;

charbuf[]=“Thisisatest”;

doublef=123.456;

mywrite(”String:

%s\nInt:

%d,%d\nFloat:

%4.2f\nEnd”,buf,i,j,f,0);

t(”aaa”,i);

}

//输出:

String:

Thisisatest

Int:

10,20

Float:

123.46

End

当然这里的解析是不完善的

详细出处:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 中考

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1