Board logo

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

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

可变参数的英文表示为:variable argument.; }/ @/ X1 L9 ?* b0 z
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
+ ]9 ~2 c6 W# z- r7 C0 P8 c可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不5 {  u' @) J. k0 L3 F1 U7 s6 U; T
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
. Y' C: [  D/ |! I0 w, ^6 ]实际的名称与之相对应.
# x( g2 q% c$ Z$ }1 a8 v由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.- _& u1 ?/ V6 F; H; _! `
然而,更多地自由,同样也加大操作上的难度.# q4 w' W- N4 A' D% o" a3 S
以下就对可变参数的几个方面作一定的介绍.
4 u; G8 z9 y4 ?& i$ J, }) g6 ?" n" E1 u- _
1)可变参数的存储形式.
' U. Y/ d/ s6 r8 v. f( }2 |4 R' d* O: }6 N. x
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
8 ]3 y' v( m' c% ?9 O6 H' V' G存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
( e% f# e, \- |( d3 r( k在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
6 N. x' t* u4 i这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.* U4 T2 L" w# g; A' U9 o
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):& U. E6 a) g/ G+ S0 U' S( U8 w2 X
栈区:
2 ~! W9 U, o4 H/ @
" c% E+ {3 }3 f; R|栈顶             低地址# d% v! M% }9 l6 z# G( T
/ u( j& x2 q3 J7 a, U. P2 X% U2 r, C
|第一个固定参数var1# H8 i4 g" Y- ]- P+ ]
|可变参数前的第一个固定参数var2
1 B9 M$ t+ _2 r  Q' x3 _2 Y|可变参数的第一个参数
0 J& ~8 {; p" e2 {|...: Q( m2 V1 c* s  D" k2 L: m
|可变参数的最后一个参数
: s6 `' h) Q3 T! n- E2 z|函数的倒数第二个固定参数var3
" \, h" _3 B' @# W9 J, }) O: k9 [0 ^# Y|函数的最后一个固定参数var4! i3 ]$ _2 E7 _9 C% y
|...  {- b: q! |6 }# o9 L5 A$ z6 B' L
|函数的返回地址: i' l9 n, h  l
|...
& S8 b: u2 i! W; A) s' f|栈底    高地址
; r! j3 G4 _/ s) H
, q! ~. G0 A/ f/ N# k' ^7 U& Z2)使用可变参数所用到头文件和相关宏说明7 q8 b# l3 O6 ?
* E8 s* n! g' }2 y4 |0 y6 b
在此,以TC2.0编译器为参考对象来说明.
& a  a1 I1 a; p% k可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
2 }4 n5 a: X2 W3 V  v此文件为:
7 l/ Y0 F- e" Y' e/ }* r% c/* stdarg.h/ M+ C  ~5 q9 H/ o, N' Q1 M4 V

+ l$ A" w. j! i2 s8 @; h( I4 CDefinitions for ACCESSing parameters in functions that accept, H7 Y7 a$ @! X+ ~/ [6 Z
a variable number of arguments.
$ K# h; u5 S. z' L5 l
* s- z6 k+ @3 j! }& M& Z- OCopyright (c) Borland International 1987,19889 T; b9 l3 z# k$ W* D, l* _+ [* e
All Rights Reserved.
% O4 C3 F: W/ ^# R  i4 ?*/" j# O% M5 ?# x2 z" j
#if __STDC__) o% P1 Q% p0 g: \) V& P2 r
#define _Cdecl  t$ [7 U5 f8 |, ]; W8 P& R
#else
( s! L1 u, _2 ]$ }" ?#define _Cdecl cdecl5 F" T  r1 X) k  r7 g2 v: f0 d& v
#endif' s3 y! e) `6 t! W* J, O
- p( l: H# j6 j
#if !defined(__STDARG)1 I6 }. }/ U2 ?' x  h7 _6 o$ M% \
#define __STDARG
/ m" w/ C. e) q( X% `. a, \0 i" I% B! X. [  p
typedef void *va_list;, V# c5 O# t- V' N

' t* [) m) J. k$ O7 t, B#define va_start(ap, parmN) (ap = ...)9 D! D5 q' t6 U6 v  n1 U) [6 B! N
#define va_arg(ap, type) (*((type *)(ap))++)" C! [# E* H- u1 J: L' b
#define va_end(ap)9 ]6 t6 o) ~' {6 c
#define _va_ptr   (...)
. Z! D3 H! ~- |" v#endif
$ |2 }. i4 b$ A2 w( X4 K  p2 K! @! e. D4 Y' a  F/ r5 b
以上为"STDARG.H"的内容.
# z7 I- a! Z  G该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
& }0 Y# z7 r( L/ P. q  D* C$ s8 p2 Sva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,4 }' ]  D! A% j6 b
parmN为可变参数的前面一个固定参数.
3 L* \' W2 B( m* Y  P& qva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.4 ]7 e% A0 ?+ u1 Z
va_end(ap) 结束可变参数获取.  Q1 m: [( z9 r4 W; D( u& N

9 A0 e- X7 |) O. D% `0 x9 N3)可变参数的使用实例
9 H. K8 Q' D. b/ {% o7 a% W0 q; E4 N3 v  P' J* ]& o6 o; D
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.8 o% G7 f& A( h* P) s+ Q1 c& x
/ Z9 `. E  h7 w
#include<stdio.h>
4 h0 F( C. D6 @; N7 ^7 n9 `#include<conio.h>" L% \4 t2 u" [* i/ \3 [
#include<stdarg.h>; Q7 I( M2 v2 p5 w3 g
void tVarArg(int num,...);/*num为可变参数的个数*/
* I2 Q% h; A/ a( J2 Q( l+ j  C- Qint main(void)
" q- P: M0 x, m9 _, ?{9 ^8 \/ F9 o) i6 W
clrscr();
( G' @; [. E2 A0 W* K; a: d" M/ EtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");0 D2 L/ Z9 p0 Z0 E
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");( `! T( l' t2 r1 P. i" x
getch();9 B& b: R& L; f' u7 a
return 0;2 G, x" B; U# V
}
  W6 N, r/ n! b9 Uvoid tVarArg(int num,...)
3 z- L/ Z8 }. A. l  q{
. V* d- @$ h1 i" ~# ?$ H$ Yva_list argp;  /*定义一个指向可变参数的变量*/
: G" x0 ?  ^4 y4 B2 G& i/ l' Eva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
$ E/ _) |6 S# H2 V6 X7 s6 \while(--num>=0)
5 m* p' Y( e8 R# p4 q- E) b  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,, p1 ^  r* v$ L: o
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/9 f4 d8 s7 T8 L, T) D3 ]
va_end(argp);  /*结束可变参数获取*/3 d# j6 V: ^5 H7 L1 I$ E
return ;# f) S* ^6 k7 u
}5 ]; l- b5 |! y& p6 G# ^

6 p9 g5 l) w5 y/ n+ U4)可变参数的使用需要注意的问题4 A% Z# B" L0 v) \) i( T

" O! J9 O% U7 ]4 {3 s1.每个函数的可变参数至多有一个.3 a2 P& `" z/ h* `4 t5 J9 [- |
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.* [+ W7 u( X3 N/ D0 v
3.可变参数的个数不确定,完全由程序约定.
6 q8 N. j) i7 O, T; u1 `+ l8 }4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
+ D! g# T/ \0 Q; M1 v) @. G而printf()中不是实现了识别参数吗?那是因为函数 3 S1 l* d/ O) D
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
0 c+ I$ [) q% j  F8 a的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 . V2 |( c  P) i" K( M0 U
过在自己的程序里作判断来实现的.
8 k5 L* d/ o* x: M5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




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