您的位置:首页 > 其它

关于函数参数表中的三个点表示什么——略做改动

2013-03-28 09:53 225 查看
标准库提供的一些参数的数目可以有变化的函数。例如我们很熟悉的printf,它需要有一个格式串,

还应根据需要为它提供任意多个“其他参数”。这种函数被称作“具有变长度参数表的函数”,

或简称为“变参数函数”。我们写程序中有时也可能需要定义这种函数。要定义这类函数,就必须

使用标准头文件<stdarg.h>,使用该文件提供的一套机制,并需要按照规定的定义方式工作。本节

介绍这个头文件提供的有关功能,它们的意义和使用,并用例子说明这类函数的定义方法。一个变

参数函数至少需要有一个普通参数,其普通参数可以具有任何类型。在函数定义中,这种函数的最

后一个普通参数除了一般的用途之外,还有其他特殊用途。

下面从一个例子开始说明有关的问题。

假设我们想定义一个函数sum,它可以用任意多个整数类型的表达式作为参数进行调用,希望sum

能求出这些参数的和。这时我们应该将sum定义为一个只有一个普通参数,并具有变长度参数表的

函数,这个函数的头部应该是(函数原型与此类似):

int sum(int n, ...)

我们实际上要求在函数调用时,从第一个参数n得到被求和的表达式个数,从其余参数得到被求和的

表达式。在参数表最后连续写三个圆点符号,说明这个函数具有可变数目的参数。凡参数表具有这种

形式(最后写三个圆点),就表示定义的是一个变参数函数。注意,这样的三个圆点只能放在参数表

最后,在所有普通参数之后。

为了能在变参数函数里取得并处理不定个数的“其他参数”,头文件<stdarg.h>提供了一套机制。这

里提供了一个特殊类型va_list。在每个变参数函数的函数体里必须定义一个va_list类型的局部变量,

它将成为访问由三个圆点所代表的实际参数的媒介。下面假设函数sum里所用的va_list类型的变量的

名字是vap。在能够用vap访问实际参数之前,必须首先用“函数”va_start做这个变量初始化。函数

va_start的类型特征可以大致描述为:

va_start(va_list vap, 最后一个普通参数);

//va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。

上面的“最后一个普通参数”也就是说va_start()的第二个参数一定要是sum()函数里面 “...” 的前

一个参数。因为从理论上,(在后面也会提到)C语言中函数的参数入栈方式是从右往左,所以我们

只要定位到“最后一个普通参数”的地址,并且知道该变量的类型,通过指针移位运算,则可以顺藤

摸瓜找到后边的第一个可变参数的地址。继而知道后面所有可变参数的地址。

实际上va_start通常并不是函数,而是用宏定义实现的一种功能。在函数sum里对vap初始化的语句应

当写为:

va_start(vap, n); //在完成这个初始化之后,vap就指向可变参数列表里面的第一个参数了。

//************************************************插****入****一****段***************************************

◎研究:

typedef char * va_list;

#define va_start _crt_va_start

#define va_arg _crt_va_arg

#define va_end _crt_va_end

#define _crt_va_start(vap,v) ( vap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

#define _crt_va_arg(vap,t) ( *(t *)((vap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define _crt_va_end(vap) ( vap = (va_list)0 )

va_list argptr;

C语言的函数是【从右向左】压入堆栈的,调用va_start后,

按定义的宏运算,_ADDRESSOF得到v所在的地址,然后这个

地址加上v的大小,则使ap指向第一个可变参数如图:

【栈顶 低地址】

| 函数第一个固定参数

| 函数最后一个固定参数

| 函数第一个可变参数 <--------va_start后vap指向

| ....

| 函数最后一个参数

| .......

| 函数返回地址

| .......

【栈底 高地址】

//**********************************************插****入****完****毕***************************************

现在,我们就可以通过另一个宏va_arg访问函数调用的各个实际参数了。宏va_arg的类型特征可以大

致地描述为:

Type xx= va_arg(va_list vap, Type);

//Type一定要相同,如:

//char *p = va_arg( ap, char *);

//int i = va_arg( ap, int );

在调用宏va_arg时必须提供有关实参的实际类型,这一类型也将成为这个宏调用的返回值类型。对

va_arg的调用不仅返回了一个实际参数的值(“当前”实际参数的值),同时还完成了某种更新操作,

使对这个宏va_arg的下次调用能得到下一个实际参数。对于我们的例子,其中对宏va_arg的一次调用

应当写为:

v = va_arg(vap, int);

(这里假定v是一个有定义的int类型变量。)

在变参数函数的定义里,函数退出之前必须做一次结束动作。这个动作通过对局部的va_list变量调用

宏va_end完成。这个宏的类型特征大致是:

void va_end(va_list vap);

下面是函数sum的完整定义,从中可以看到各有关部分的写法:

int sum(int n, ...) {

va_list vap;

int i, s = 0;

va_start(vap, n);

for (i = 0; i < n; i++) s += va_arg(vap, int);

va_end(vap);

return s;

}

这里首先定义了va_list变量vap,而后对它初始化。循环中通过va_arg取得顺序的各个实参的值,并将

它们加入总和。最后调用va_end结束。

下面是调用这个函数的几个例子:

k = sum(3, 5+8, 7, 26*4);

m = sum(4, k, k*(k-15), 27, (k*k)/30);

在编写和使用具有可变数目参数的函数时,有几个问题值得注意:

首先,虽然在上面描述了头文件所提供的几个宏的“类型特征”,实际上这仅仅是为了说明问题。因为

实际上我们没办法写出来有关的类型,系统在预处理时进行宏展开,编译时即使发现错误,也无法提供

关于这些宏调用的错误信息。所以,在使用这些宏的时候必须特别注意类型的正确性,系统通常无法自

动识别和处理其中的类型转换问题。

第二:调用va_arg将更新被操作的va_list变量(如在上例的vap),使下次调用可以得到下一个参数。

在执行这个操作时,va_arg并不知道实际有几个参数,也不知道参数的实际类型,它只是按给定的类型

完成工作。因此,写程序的人应在变参数函数的定义里注意控制对实际参数的处理过程。上例通过参数n

提供了参数个数的信息,就是为了控制循环。标准库函数printf根据格式串中的转换描述的数目确定实际

参数的个数。如果这方面信息有误,函数执行中就可能出现严重问题。编译程序无法检查这里的数据一

致性问题,需要写程序的人自己负责。在前面章节里,我们一直强调对printf等函数调用时,要注意格式

串与其他参数个数之间一致性,其原因就在这里。

第三:编译系统无法对变参数函数中由三个圆点代表的那些实际参数做类型检查,因为函数的头部没有

给出这些参数的类型信息。因此编译处理中既不会生成必要的类型转换,也不会提供类型错误信息。考

虑标准库函数printf,在调用这个函数时,不但实际参数个数可能变化,各参数的类型也可能不同,因此

不可能有统一方式来描述它们的类型。对于这种参数,C语言的处理方式就是不做类型检查,要求写程

序的人保证函数调用的正确性。

假设我们写出下面的函数调用:

k = sum(6, 2.4, 4, 5.72, 6, 2);

编译程序不会发现这里参数类型不对,需要做类型转换,所有实参都将直接传给函数。函数里也会按照

内部定义的方式把参数都当作整数使用。编译程序也不会发现参数个数与6不符。这一调用的结果完全

由编译程序和执行环境决定,得到的结果肯定不会是正确的。

可以定义以va_list作为参数的函数,这里就不举例子了。

转自:http://hi.baidu.com/zcube/item/5a50cd12ccade06f71d5e8cf

参考:http://feimeng2000.blog.163.com/blog/static/22351950200851935239151/
http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: