当前位置:天才代写 > tutorial > C语言/C++ 教程 > c/c++支持可变参数的函数

c/c++支持可变参数的函数

2017-11-05 08:00 星期日 所属: C语言/C++ 教程 浏览:597

副标题#e#

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

一般我们编程的时候,函数中形式参数的数目凡是是确定的,在挪用时要依次给出与形式参数对应的所有实际参数。但在某些环境下但愿函数的参数个数可以按照需要确定,因此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        ;挪用者认真清栈

假如挪用函数的时候利用的挪用协议和函数原型中声明的纷歧致,就会导致栈错误,这是别的一个话题,这里不再细说。

别的c/c++编译器回收宏的形式支持可变参数函数。这些宏包罗va_start、va_arg和va_end等。之所以这么做,是为了增加措施的可移植性。屏蔽差异的硬件平台造成的差别。

支持可变参数函数的所有宏都界说在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, ……);


#p#副标题#e#

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

一般来说,这种形式至少需要一个普通的形式参数,可变参数就是通过三个’.’来界说的。所以"……"不暗示省略,而是函数原型的一部门。type是函数返回值和形式参数的范例。

譬喻:

int MyPrintf(char const* fmt, ……);

可是,我们也可以这样界说函数:

void MyFunc(……);

可是,这样的话,我们就无法利用函数的参数了,因为无法通过上面所讲的宏来提取每个参数。所以除非你的函数代码中简直没有用到参数表中的任何参数,不然必需在参数表中利用至少一个普通参数。

留意,可变参数只能位于函数参数表的最后。不能这样:

void MyFunc(……, int i);

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

type funcname(va_alist);

可是要求函数实现的时候,函数名字后头必需加上va_dcl.譬喻:

#i nclude <varargs.h>
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尺度名目。

#p#副标题#e#

四、可变参数函数的根基利用要领

下面通过若干例子,说明如何实现可变参数函数的界说和挪用。

#p#分页标题#e#

//================================ 例子措施1 ===============
#i nclude < stdio.h >
#i nclude < string.h >
#i nclude < stdarg.h >

/* 函数原型声明,至少需要一个确定的参数,留意括号内的省略号 */
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 是: %s\n", argno, para);
     argno++;
    }
va_end( argp ); /* 将argp置为NULL */
    return 0;
}

//输出功效
参数 #0 是: This
参数 #1 是: is
参数 #2 是: a
参数 #3 是: demo!

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

//================================ 例子措施2 ===============

#i nclude <stdio.h>
#i nclude <stdarg.h>
int average( int first, ... ); //输入若干整数,求它们的平均值

void main( void )
{
   /* 挪用3个整数(-1暗示末了) */
   printf( "Average is: %d\n", average(2,3,4, -1));

   /*挪用4个整数*/
   printf( "Average is: %d\n", average(5,7,9, 11,-1));

   /*只有竣事符的挪用*/
   printf( "Average is: %d\n", 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: 3
Average is: 8
Average is: 0

#p#副标题#e#

五、关于可变参数的通报问题

有人问到这个问题,如果我界说了一个可变参数函数,在这个函数内部又要挪用其它可变参数函数,那么如何通报参数呢?上面的例子都是利用宏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 <stdio.h>
#i nclude <stdarg.h>

int mywrite(char *fmt, ...)
{
     va_list arglist;
     va_start(arglist, fmt);
     return printf(fmt,arglist);
}

void main()
{
int i=10, j=20;
char buf[] = "This is a test";
double f= 12.345;
mywrite("String: %s\nInt: %d, %d\nFloat :%4.2f\n", buf, i, j, f);
}

运行一下看看,哈,错误百出。仔细阐明原因,按照宏的界说我们知道 arglist是一个指针,它指向第一个可变的参数,可是所有的参数都位于栈中,所以arglist指向栈中某个位置,通过arglist的值,我们可以直接查察栈内里的内容:

arglist -> 指向栈内里,内容包罗

0067FD78 E0 FD 67 00 //指向字符串"This is a test"

0067FD7C 0A 00 00 00 //整数 i 的值

0067FD80 14 00 00 00 //整数 j 的值

0067FD84 71 3D 0A D7 //double 变量 f, 占用8个字节

0067FD88 A3 B0 28 40

0067FD8C 00 00 00 00

假如直接挪用 printf(fmt, arglist); 仅仅是把arglist指针的值0067FD78入栈,然后把名目字符串入栈,相当于挪用:

printf(fmt, 0067FD78);

自然这样的挪用必定会呈现错误。

我们能不能逐个把参数提取出来,再通报给其它函数呢?先思量一次性把所有参数通报进去的问题。

#p#副标题#e#

#p#分页标题#e#

假如挪用的是系统库函数,这种环境下是不行能的。因为提取参数是在运行态,而参数入栈是在编译的时候确定的。无法让编译器预知运行态的工作给出正确的参数入栈代码。而我们在运行态固然可以提取每个参数,可是无法将参数一次性全部压栈,纵然利用汇编代码实现起来也是很坚苦的,因为不光是一个简朴的push代码就可以做到。

假如接管参数的函数也是我们本身写的,自然我们可以把arglist指针入栈,然后在函数中本身理会arglist指针内里的参数,逐个提取出来处理惩罚。可是这样做好像没有什么意义,一方面,这个函数没有须要也做成可变参数函数,另一方面直接在第一个函数中理会参数,然后处理惩罚不是更简朴么?

我们独一可以做到的是,逐个理会参数,然后轮回中挪用其它可变参数函数,每次通报一个参数。这里又有一个问题,就是参数表中的不行变参数的通报问题,有些环境下不能简朴的通报,以上面的例子为例, 凡是我们理会参数的同时,还需要理会名目字符串:

#i nclude <windows.h>
#i nclude <stdio.h>
#i nclude <stdarg.h>

//测试一下这个,开个玩笑
void t(...)
{
printf("\n");
}

int mywrite(char *fmt, ...)
{
va_list arglist;
va_start(arglist, fmt);

char temp[255];
strcpy(temp, fmt); //Copy the Format string
char Format[255];

char *p = strchr(temp,'%');
int i=0;
int iParam;
double fParam;
while(p != NULL)
{
   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(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);

return i;

}

void main()
{
int i=10, j=20;
char buf[] = "This is a test";
double f= 123.456;
mywrite("String: %s\nInt: %d, %d\nFloat :%4.2f\nEnd", buf, i, j, f, 0);
t("aaa", i);
}

//输出:
String: This is a test
Int: 10, 20
Float :123.46
End

虽然这里的理会是不完善的。

 

    关键字:

天才代写-代写联系方式