  
- UID
- 133
- 帖子
- 51
- 精华
- 1
- 积分
- 186
- 金币
- 55
- 威望
- 2
- 贡献
- 0

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.: q9 A$ B) J$ E8 T/ J/ k- W0 h
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
3 o* Y& C+ Y5 B. o可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
. D$ N( M q( G; v定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有# v/ Q7 U7 c, \0 u9 m6 M! v
实际的名称与之相对应.* {- r, V% m- E& D' ?+ \
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.* D" E! Z" h U( U
然而,更多地自由,同样也加大操作上的难度.
4 `) O7 X) F3 N$ w以下就对可变参数的几个方面作一定的介绍.
+ Z/ x5 o+ i9 X- Y- G6 f1 o2 d! M2 t* Q5 t8 z; n
1)可变参数的存储形式.4 ?/ A7 R% K1 `- \3 p B2 k' J. W. w
' p* V+ |3 j1 u
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
* v5 y7 @+ Z# e) g+ c8 s$ v存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
" K0 x9 @* a0 n- @: x: m" t( u在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
1 n/ u7 \! b+ g0 O2 W+ I这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存., S9 H8 m4 l0 l' ^
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
' K. e5 `5 @, m# Q0 b栈区:2 x8 h" {( c' o
/ l; k0 R$ B1 h3 T m
|栈顶 低地址
. Z! o9 W! |9 v% {, N& `! W+ Y5 r+ j u
|第一个固定参数var1) p2 c8 K' v/ R% G5 T ?
|可变参数前的第一个固定参数var2/ `, s. T% X7 A" I- a' ~' N8 J0 n
|可变参数的第一个参数8 H; g# m! M+ e$ }8 d! V; Y: j
|...) B6 M& b( n9 }) G2 M, P
|可变参数的最后一个参数
0 b( k& y2 l8 r* r3 `|函数的倒数第二个固定参数var3" c" b' G' `( A8 M8 O* g1 X6 ~
|函数的最后一个固定参数var4. h& [6 j# z$ s7 f! l0 ?! w
|...
& r3 e* ~! }/ @1 d5 }0 l|函数的返回地址
* k- s. r1 `2 t& N# l4 n|...
% E# {4 V2 V* {$ }4 H" ?8 m& z|栈底 高地址
' |4 i5 S0 Z8 A" d8 Z$ I1 D4 p2 o5 [& N
2)使用可变参数所用到头文件和相关宏说明# v5 p( o% T! G
# R5 M1 N2 D0 z/ `2 p2 w在此,以TC2.0编译器为参考对象来说明.
% n% J8 w# t+ G8 n可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.$ g9 R9 [! n- M# {, s$ M+ ^
此文件为:
6 k6 B9 O$ a- I _ h6 Y# \/* stdarg.h" l/ d/ m0 X/ x# L+ R' P* L9 R
3 h- U& O" @1 X6 i# ]" ?$ b2 L1 wDefinitions for ACCESSing parameters in functions that accept
. ~/ E( c, ~" T* ]" w9 L2 f) x( z; a6 t( Ma variable number of arguments.' c. }2 {( `' M3 K( N
$ G( W* l+ T3 hCopyright (c) Borland International 1987,1988
, ?9 N- W& q) Y* ]8 `. V8 ZAll Rights Reserved.
) d, b: _+ w$ e- c: K E @# ?; i" c*/
. o) D% I( z' T8 x, T# f( I2 M#if __STDC__2 C& O6 |$ T# C# W! |' J6 m
#define _Cdecl8 m* w, y7 O4 V" @! O
#else- X/ ~9 K' c9 t1 S. S* J. ^
#define _Cdecl cdecl9 j+ I7 a8 J0 C0 I( Q% R
#endif
" U4 \" B$ ^/ W* J9 `1 R) y1 Q
7 r# l% G2 V0 P6 `8 `7 S3 y! J+ p#if !defined(__STDARG), L( `$ I5 y4 c; [2 w; D- ^
#define __STDARG
9 i% |8 A- X/ Q& N+ V) {* Q# P: \' R3 V; C5 y; L4 Q- E8 t' c; w
typedef void *va_list;
% j- Y4 \. j4 r: V0 [9 `2 P6 g7 |; @
#define va_start(ap, parmN) (ap = ...). I3 ^% `6 |1 u# B
#define va_arg(ap, type) (*((type *)(ap))++). h: T; I9 o: W8 X
#define va_end(ap)
$ D8 V6 L' R6 q#define _va_ptr (...)* b/ M/ m9 @8 A* |: F" }
#endif- x( C k$ x# H1 F+ r
0 \. T& {& U1 q8 a! m: W* J3 ~以上为"STDARG.H"的内容.
2 S& |& H9 {* u8 j; G" Z! g: X该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;% ~' f: h/ N: i8 F! N3 e8 L
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
1 o. d0 W! ?- H4 w9 vparmN为可变参数的前面一个固定参数.
5 E' h x$ B2 D0 U# Rva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
. |0 y% z* H" N% Qva_end(ap) 结束可变参数获取.# R; S) M: t; Q0 ~. ~
$ d# r2 ~8 I$ ]! j+ Q' B5 o3)可变参数的使用实例
, F# y7 I! S) ?' c( ^& {4 g+ B6 o0 `* U0 |" f5 c' V
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.( } ~' X V0 J$ n! V4 N
; m0 @9 \; u8 Q( o1 ?* T
#include<stdio.h>; R8 ~/ ~8 T9 r- l1 R. w! ~4 q4 r8 b
#include<conio.h>
/ Q* c, d6 H4 x) t( ^#include<stdarg.h>
2 S! | A0 ~" O8 l4 Q: Ovoid tVarArg(int num,...);/*num为可变参数的个数*/
6 C7 ?# t' Q& L% s! Dint main(void)7 E' V! l* w, f7 m& }" Z
{
' f" }( h8 h" `9 g6 F* W m3 R% kclrscr();5 V; V. C7 o$ s( i9 w/ Y4 Y d: m7 t
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
& u/ ^% w2 A* @+ U% G6 ^tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");( \# V9 U5 w7 {$ K% j- a
getch();
9 k9 {9 }6 s I1 {return 0;
+ h W3 a9 i8 y( A7 M}
/ V2 A: h; e! q( I) F4 A# ivoid tVarArg(int num,...)
|- T Z, i& s$ ]) J# c{7 h* w5 w* r5 w
va_list argp; /*定义一个指向可变参数的变量*/
; t9 F* l q; dva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
1 _; o8 a# b4 y% V% D7 dwhile(--num>=0)
c' q1 i9 z9 B) s3 W printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数, |6 v# z9 E: S& a* v
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/( O: O) q3 C- T4 F
va_end(argp); /*结束可变参数获取*/
* X0 v/ n. U% |. c& ureturn ;. M/ }6 z: ^# o
}
' i6 b- X$ h) D! R8 M+ [
* H4 I' g4 D# c0 p; M3 R6 \4)可变参数的使用需要注意的问题
$ p) @% I4 O/ A2 }/ K) N
7 X: g/ n5 g' B5 o, u1.每个函数的可变参数至多有一个./ A! ~$ _! }" ^8 d& U" c
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.' H# O* }( p0 ~3 A
3.可变参数的个数不确定,完全由程序约定.6 X2 X3 _- g% I7 h! D# t4 a
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
: C } B2 @4 x7 C而printf()中不是实现了识别参数吗?那是因为函数 / d3 l; K) a% T% K" E0 D7 ^! ^
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
r. ]' T6 t# V: b( M& ^* H, C的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ' h' v7 C% h1 ?4 I
过在自己的程序里作判断来实现的.
5 R5 k* M. V5 @9 K$ A) s9 c; [5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|