Board logo

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

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

可变参数的英文表示为:variable argument.3 d2 h6 l: q. G2 m$ ?
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
; D" ?* ^0 j$ x3 d. N可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
: X) [2 L) e, K1 l$ a& d定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有0 |' s2 j' b& p. j$ H8 y3 b
实际的名称与之相对应.2 i4 D9 n; R$ r$ D
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
2 _/ }  }1 X$ |! g然而,更多地自由,同样也加大操作上的难度.  w% P( r8 J, P" X+ B  A
以下就对可变参数的几个方面作一定的介绍.
; |$ J8 r/ ?1 ]
+ {5 q6 ?7 c! ]( @. w! W1)可变参数的存储形式.
. x; ~: ?- k; c
! }9 n( Z2 W0 U* `4 e8 |6 c3 ~  _大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
9 Q. k4 n+ d0 s2 ^8 q# ?存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
/ g, x7 w# E( S, J$ u1 G( x# J在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
; {# J, E1 _* Y/ m% n* l& x6 Z; T这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
  Z3 T7 \4 E* X( [0 p因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):: G% Y# S! k( B9 w) D% W
栈区:1 w8 d5 t# k5 Q. f& W: s3 O
5 w( L0 N9 \8 k& Q2 {4 E
|栈顶             低地址
) h# l, R- }2 S* ]/ {+ Z
/ Z7 H  b" W/ P" e! {  q* I|第一个固定参数var1
- X% J0 ?2 {6 z4 r|可变参数前的第一个固定参数var2& h$ F" _: O& O; y+ \/ F# l, b# g
|可变参数的第一个参数3 r* |" g/ _( U0 G: e$ ]
|...! h; a% @; d$ f' [1 K) Z8 a8 R, ?
|可变参数的最后一个参数7 V% g! |/ h+ L7 n2 p, T  v& ^' x# ]
|函数的倒数第二个固定参数var3
. e) ]9 ]0 j% m0 I0 d* _|函数的最后一个固定参数var4" t( G/ V$ V4 K8 c2 z6 U
|...
. ~' o& C7 d0 r9 A4 u5 g: U|函数的返回地址. `8 W! J2 b4 c+ p7 Z( W
|...* C' e! }5 l) `" o0 P; ]: R& u
|栈底    高地址
" M1 u) q4 Z- I& t* y
; [$ L9 E! w" w2 g2 O  ]. i2)使用可变参数所用到头文件和相关宏说明
+ ?7 i1 O6 R7 K3 M
( E1 [) G3 Y1 n' H' ~% T, m; T在此,以TC2.0编译器为参考对象来说明.
' y0 \6 G4 u$ p* O. W: Q( |可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
; A3 C- T/ |; c- s$ Q此文件为:8 w3 }) W1 {3 Z* d3 `1 R% f
/* stdarg.h
+ N9 `& J% a# l3 F' L& f, m; a* S& l! d0 L) N! G5 H1 Q7 ^! e2 R6 K
Definitions for ACCESSing parameters in functions that accept
" j: s4 S7 \) H/ }" G' g  oa variable number of arguments.
4 r' [( L  C& A! y) O9 q" `9 G4 {/ N9 d4 F0 |: m
Copyright (c) Borland International 1987,1988
3 m2 |; A; {% I5 I' `3 AAll Rights Reserved.
- b, {) q! u6 c% Y*/2 Y8 [, Z9 W2 r- t2 e+ t4 M
#if __STDC__
8 S% i! b+ d3 V4 ~) ]$ W! C#define _Cdecl
2 H' p5 K5 J* J8 Y; T0 d7 y5 k8 a#else9 s6 ?& @) y2 Z0 P* a0 z/ c
#define _Cdecl cdecl7 E  C' ~# |+ E! y+ F
#endif
( P; {7 [1 t/ M* u) I# S) C/ z$ i2 f, z1 d' R) x
#if !defined(__STDARG)
8 `9 p; P9 D4 {2 [9 K# J#define __STDARG* }9 m. }+ H8 m" `* n- p
) `! u  r5 C$ P5 e. Y5 L7 x
typedef void *va_list;* K. R  o+ d: T

/ z# W4 T6 D( b# a) i1 _#define va_start(ap, parmN) (ap = ...)8 g( `- ^" H  p6 H2 Z  D6 L
#define va_arg(ap, type) (*((type *)(ap))++)  X- y" C' s" x  L. ]9 E
#define va_end(ap)
/ Z# g8 x0 M0 Z, b; e2 l$ ^, h#define _va_ptr   (...)
+ W/ T9 y- d2 @* {5 E2 U4 D#endif
. I+ Y+ q  I2 s5 t1 Z: q( A9 d: F$ i% @! T' [
以上为"STDARG.H"的内容.
" ]3 n4 N* j5 [; M" k该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;( c( U% W: ]" k; v* R& o
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
1 ^2 a; D6 \2 ^" a3 S8 @parmN为可变参数的前面一个固定参数.. a* m; P# [! A5 o, ~0 A" A. a; P
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
! s4 `9 t5 d5 O* S( @# dva_end(ap) 结束可变参数获取.
4 f9 }% }8 Y5 y+ ], f/ d1 ?9 x5 A$ g! J  @" ^
3)可变参数的使用实例
1 d# k0 y  h) w3 R+ y9 D1 E
6 N! g, ]4 n2 y实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.3 s& w* i4 P7 a7 o4 Z: s
' Z# s+ g( I% b# Y
#include<stdio.h>
& c* i5 q! U; v& g3 l& l2 E#include<conio.h>1 j' J' C: H  Q. j9 p! x3 d
#include<stdarg.h>
, J' D2 h8 F1 z8 g/ `7 @  gvoid tVarArg(int num,...);/*num为可变参数的个数*/
" y& D4 C. K3 R! \int main(void)7 @# w1 j4 f  g1 F
{
1 o4 f/ c. G4 u  S$ fclrscr();, O; X- l7 |0 H5 ], J
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
- {+ n2 L! r+ g% A( vtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");: s- r/ F/ u4 B5 }
getch();4 k4 i: J, N$ F! h. O0 r
return 0;( p# s1 G0 ]7 r* n9 f6 l8 s& c
}! M+ H; ]. b% n6 T) V7 @4 P
void tVarArg(int num,...)
1 e3 T& f5 X" o3 l2 T: G{1 l0 |- X$ H0 M
va_list argp;  /*定义一个指向可变参数的变量*/" ^& Z% C# S' |' G8 Z) Y
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
! I! w: }, v" v6 Z% }0 Ewhile(--num>=0)
6 }6 ]& I  h6 n  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,! p: u; j0 z8 \$ [
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/, ^1 }$ a: X" R% h5 o
va_end(argp);  /*结束可变参数获取*/
- N6 Z0 _0 |! q) m2 Q( i3 Areturn ;
* K7 k3 W( b' C}$ S$ d3 `. c! Y+ i+ n2 J+ B

6 L% _1 |+ Z' X  D5 D$ `- |4)可变参数的使用需要注意的问题" A  f, j8 j, B- [$ `# C% x

& H% n# p. K% c$ y1.每个函数的可变参数至多有一个.
! K' N. I% Y" h1 h5 f: G2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
6 s! m4 n9 x- I3.可变参数的个数不确定,完全由程序约定.
. n, `' Q# ^, z. `0 z: A4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
; g' M4 N, L: [而printf()中不是实现了识别参数吗?那是因为函数 & r  K( k+ O9 s; m6 M, D9 C
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg * {# S( b+ s6 {: o0 |
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
1 r1 K* i7 w) L8 ^过在自己的程序里作判断来实现的. 0 H5 ?0 ?$ G/ ^. g4 k
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




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