Board logo

标题: 函数的可变参数详谈 [打印本页]

作者: zw2004    时间: 2008-1-21 19:42     标题: 函数的可变参数详谈

可变参数的英文表示为:variable argument.4 X2 V# j( H$ {- T9 K- q
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
% N. S+ I& L5 h% z2 |; \$ {7 t% g可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不  B; @4 Y4 L5 }: y9 f- f& H
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有: S& X2 s1 \8 d9 V- E/ e0 k/ ]1 f
实际的名称与之相对应.% l5 Q; P" Y$ N6 c# s+ t5 n/ D
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间./ k1 r. e% M6 H% I6 X/ M
然而,更多地自由,同样也加大操作上的难度.
1 u! r3 l! n% J1 o- x/ C) g以下就对可变参数的几个方面作一定的介绍.
4 ]* n2 I& u6 z- ~% Y( t
- m: }/ _, a: T/ I: \/ E* J0 q1)可变参数的存储形式.0 `8 n$ Z8 B! H: p( K0 e

- m+ _9 e8 @# l0 C, c  Q大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
& _+ T7 Z! J. g  o存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.8 t- e3 I5 {6 t, F
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,& t  s3 n5 T5 K& f( r, e9 c
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
; B' {% B6 s/ T) }0 q$ R3 f9 D3 R因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
! l1 i$ _4 Z9 g* w栈区:
; R. T+ r  x  }5 V
6 O" W1 y; X' K1 N7 d|栈顶             低地址8 n/ M$ R& i+ \8 B

: E# p7 r: c6 ^3 d|第一个固定参数var1
+ Q. f3 B4 n3 z|可变参数前的第一个固定参数var29 {) r6 O+ @5 B! e. f+ f; C2 q
|可变参数的第一个参数
7 V4 F- d: B3 o7 \, x7 c& w|...+ U6 T' n8 x" G4 n% k) D: x
|可变参数的最后一个参数
/ Q/ s4 ~$ \+ C% }1 Y9 s|函数的倒数第二个固定参数var3
4 s. O/ M- ~# A5 X4 B|函数的最后一个固定参数var4
* o7 `' x0 M' x  I! }|..., N, o9 D" T2 U) }) Q6 Q) a+ c
|函数的返回地址
+ t! T+ H" {( M( f" G7 b|...9 m: Z6 j9 u# ?( @
|栈底    高地址
6 i1 c. x2 }. X5 p' w* g- n1 o- v
2)使用可变参数所用到头文件和相关宏说明
- ^. \* F3 n4 `* m4 s- t8 p: l2 u# \3 q: \1 k
在此,以TC2.0编译器为参考对象来说明.
+ Q- `+ t2 z7 h可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
4 E- G# {9 s% a7 y; m3 h此文件为:) W+ x4 [' Z) y+ W$ l2 Z
/* stdarg.h4 [. B( q8 Y3 `+ d( k1 j6 X5 U
1 ?3 c0 ^) e& ^1 m( }# H: j
Definitions for ACCESSing parameters in functions that accept
( ], y0 \) H3 b, Ta variable number of arguments.
# ^$ y- t, T+ t5 Z4 P5 M4 k" G' y& c- M" @" K8 T* k
Copyright (c) Borland International 1987,1988
  i. V' N0 u0 y7 K0 {All Rights Reserved., [' j. h- z& A% T
*/
/ |0 |" I1 U3 }% q/ m% r3 d#if __STDC__
; ^$ p4 G8 J' b- H4 u#define _Cdecl( B' i% G6 L1 V
#else
9 V( X! I6 t  n+ @! m; y#define _Cdecl cdecl" d; \& p( u. ]0 C6 D7 w. L2 `
#endif
+ t9 L! v- V2 b) L4 p$ W# a! h2 E
$ s/ U% `% J* S) M6 {# u" L#if !defined(__STDARG)
3 w- U# y) B7 W5 [+ h# w#define __STDARG/ ]+ E- r/ y* r

& d4 Z# c0 e% d/ q* w/ Atypedef void *va_list;
! d1 ^6 T5 `; G8 v* K1 P! I7 `2 ]; Z6 {9 q0 p
#define va_start(ap, parmN) (ap = ...)
( G" J& R8 T( b& ]! E. _- [# l#define va_arg(ap, type) (*((type *)(ap))++)
8 R5 G  u+ e7 I' f( D#define va_end(ap)
" l  c( m1 x5 m3 M#define _va_ptr   (...)
. i0 Q8 U& k9 S; m#endif9 i5 O0 c" L  P/ d6 i7 f0 |& O
3 H$ _8 m' Z8 ?: W! D
以上为"STDARG.H"的内容.
5 U2 v! F9 f; x; _/ C7 I该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;1 [* i6 `- ?3 a% }" y3 b" q
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
6 ~  X: T: L. cparmN为可变参数的前面一个固定参数.. C) Z3 W# J9 k2 W) ]( w
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.7 p: X  Z% D! k- m" F1 e
va_end(ap) 结束可变参数获取.
8 z4 ~: T/ Y+ E  R1 q$ _2 G
8 Y4 }( ^  u/ h; ]8 M. t; Z3)可变参数的使用实例# ~2 K/ o8 j% T1 g+ @5 T' \

% z+ J# p& `7 ]( l: M, f实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
: P7 V6 [4 F  X$ G6 X- b/ {; p3 Z5 v# d  U" g# ]" d+ ^; O
#include<stdio.h>+ D# f3 X( F0 H( I3 U8 o9 G3 w
#include<conio.h>
, s: i# T$ L4 }0 q7 j2 s+ F#include<stdarg.h>
( B6 R; c7 {  V, \* dvoid tVarArg(int num,...);/*num为可变参数的个数*/
2 d2 X- S/ N8 p: J0 }int main(void)
+ @) [: T: W7 ~! P{
6 i8 j* x7 h5 G' \5 {2 ~2 K7 H0 M, oclrscr();& ], X' z9 C8 c5 x
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");* E% c) S; @* O; o& h; F2 U9 j6 P
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");) C: D; H0 D  \/ `: s. J( ^
getch();! }* u1 |. q2 o0 G* ~+ m0 \
return 0;
  E+ W' d9 ^- _6 V8 |3 h; l}
7 a. s0 u% o8 ^void tVarArg(int num,...)( A+ K5 P2 a: @2 _; `; ~: {1 ?
{
! Y8 F9 \4 E. X9 R0 Hva_list argp;  /*定义一个指向可变参数的变量*/: K" C# q, b0 {. S9 ?8 v8 f
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
0 e6 _% n' G6 `. w  cwhile(--num>=0)7 ?( R* ?, d+ I  o% ?
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
' k  L! K6 b$ z6 p7 ?    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
  \) o1 b1 P# F6 Ova_end(argp);  /*结束可变参数获取*/
4 p7 q4 ^: ^5 M( r' q  b4 Ereturn ;! ~1 S' G3 s" Z) ?2 [" U* q$ s
}
% {5 V9 _' A  q' T0 F5 i2 t! m* u* }& h
4)可变参数的使用需要注意的问题: [6 C6 K$ F/ ^; p+ z

* \0 T% h" H4 n) }/ F" e4 U+ ]1.每个函数的可变参数至多有一个.
7 L6 ~4 M: y  b, y8 W2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.& i' l+ _7 u; {* |
3.可变参数的个数不确定,完全由程序约定.
0 s6 u. U4 q. i& r& D& s, |7 H$ Y4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.% _0 v- Q4 [0 u( l6 i# ?
而printf()中不是实现了识别参数吗?那是因为函数 : z. l4 H5 C: A$ ^) y! ?! k! |. p
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 4 `. R/ l! N' M: T  @& Y* T! d
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 $ X! [3 G7 s2 l5 F8 j) y- x% N
过在自己的程序里作判断来实现的.
/ B5 F+ k5 S3 }) H5 U5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




欢迎光临 捌玖网络工作室 (http://89w.org/) Powered by Discuz! 7.2