捌玖网络工作室's Archiver

zw2004 发表于 2008-1-21 19:42

函数的可变参数详谈

可变参数的英文表示为:variable argument.K fv1A2[cfN
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
(C)K:e1m;d1{#J;M\h 可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
c'er fz 定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
4])IIh|TmIJZ8J 实际的名称与之相对应. N X6x/uM!@I
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
,R k.{p9FQ 然而,更多地自由,同样也加大操作上的难度.
[aZ[7_J 以下就对可变参数的几个方面作一定的介绍.
~E^&c.k ~P:f0T | l
1)可变参数的存储形式.
@[n4Q|&DS'n:X-s O~$K pV(|1LVl
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,#k A\ Fx
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.J(g N"N&T] X$t F&?-Y
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,k'b2rU y)W6{(N
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
y)K-N(@$O%Bk 因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):C%Y e0x a
栈区:%~)id t~!r,_&Nq i

7N rjh TM} |栈顶             低地址
H `aWd#YSP !{1} wVz.SV
|第一个固定参数var1)fQM'X5j.V
|可变参数前的第一个固定参数var2
"pw+_ ej7kHz? |可变参数的第一个参数}.t2}Z/o{q9r
|...
V$a z(\U |可变参数的最后一个参数`,]J+BW0D
|函数的倒数第二个固定参数var3
le$E~&W |函数的最后一个固定参数var4Jx:@-Rl*D
|...h4z4e\~e z9Q
|函数的返回地址
8e5R%B_%N1t+c |...yM&{jV"S
|栈底    高地址
1B7|%@Nm1~*y$FV A\R1P O[1p
2)使用可变参数所用到头文件和相关宏说明/{ q(x`2VU8}
⁣BP7S+g{^
在此,以TC2.0编译器为参考对象来说明.
l` f c&{.nu 可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
7@ d%a,HH^E 此文件为:eBX+J6D"P-jPn6K/z
/* stdarg.h
b,H-[ z ~i8H+wP~ 6~0Y&P_f:u0UqH
Definitions for ACCESSing parameters in functions that accept
/Dc2Y#v K2ee a variable number of arguments.(Lk*fb U:W!O!r

K vM s8qDk(`+~ Copyright (c) Borland International 1987,1988
BP{;sE&]w:l8~ G All Rights Reserved.
%c$k4Lf0}O5^n2{8M F */4i-S)O0l%o,]4|4ym)n n2L
#if __STDC__
9qS_1jX)_M|"r #define _Cdecl
(e:yn1p4j+n,Xk&H| #else
|n ` p$X*c s"R,\ #define _Cdecl cdecl
MD U x,rp #endif?6e.A zf Sc

+K`Lj#vP,c Q #if !defined(__STDARG)
8_V!j!Y;v1n4G] #define __STDARG+u z7W)jXm9o;I2D
-V7[^w@I%j
typedef void *va_list;
1@6rx&qKM
IT@ eU} #define va_start(ap, parmN) (ap = ...)
!R+hqr9tN+G2G #define va_arg(ap, type) (*((type *)(ap))++)W5Yhm G}&Mo.T.m Rp
#define va_end(ap)]!udCW.B E
#define _va_ptr   (...)
$T }'b"P"F{u&A:Uq #endif
YU{l|k/_ t g%NJR2\#u*h
以上为"STDARG.H"的内容.
4l h6uj)e#M 该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;+h F i!|9p5ktQ%L
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
*h7M Ke,~4lLU(W'J parmN为可变参数的前面一个固定参数.
M XY8T3A v va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
0G@#@*n|7wUbx va_end(ap) 结束可变参数获取.i4[mj9R

9\;hhygs 3)可变参数的使用实例
n5BiqH8e7cr
rc^"tk eh 实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.f;RIi!@v x9V9v9m$h{4`

9i3P1a%V~&Us)k #include<stdio.h>
P |w(KL.e'B #include<conio.h>7m y{F*z+q
#include<stdarg.h>
+k F y2[ }*}z void tVarArg(int num,...);/*num为可变参数的个数*/
"j+Vo;S2X.i;s cK#Hs int main(void)
.q'g2V6_I {
w&X2[EHx} Rw clrscr();G4` \nd8C4H;Ru
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
)Sx*`O SB tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");4XR+QQ"I7]8}
getch(); z"wB?E7H [
return 0;dQ'u3Sq
}
2lH|4yWJ JE(_)p void tVarArg(int num,...)
o ]j^k'a#M+M {3qe"RF;~`Z/F
va_list argp;  /*定义一个指向可变参数的变量*/+ci2gq6j
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/2h9o/Do$zB
while(--num>=0)
/`o i1i(t;|!j   printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
l&UY4Yg"i9B+M     并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
I f0nG4Z~Eu1d va_end(argp);  /*结束可变参数获取*/
#g R"` [s return ;
y-Aj:dNE1E }
6gA4P?"Sd&_0O .tHqj#h F7x
4)可变参数的使用需要注意的问题s6w;A/?b/A

G~.`I/W$? 1.每个函数的可变参数至多有一个.
em6pa`4IT/\6b4A[/L 2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
0Bx iz r.L 3.可变参数的个数不确定,完全由程序约定.7Z5K.mh-~ { V]N7Z
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.3Z*A*~*Wc {^~
而printf()中不是实现了识别参数吗?那是因为函数 JvkI2t)P1Zu+S h
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg H q|:irgzH9`
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 (_l8^!Gb_
过在自己的程序里作判断来实现的. B[SEh`p&p.U:F
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

页: [1]
【捌玖网络】已经运行:


Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.