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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
9 ^0 k% S% ]( `8 c4 V& y8 |它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
% t6 m" g+ d5 i可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
- \$ h/ A) U) [4 x6 v定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有0 p$ Z1 f$ P2 K; B2 {! B% E' t
实际的名称与之相对应.5 Q) P, C/ d( v: \
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.& w) S. E- d* t- Z' E* ]
然而,更多地自由,同样也加大操作上的难度.
+ |$ D8 X& s3 K( S/ E9 L% q2 O4 K以下就对可变参数的几个方面作一定的介绍.
" {0 {' C* j, T- e" i9 B5 `6 w) ~
1)可变参数的存储形式.8 v2 ~% t7 h" l1 \0 m7 k
' n* I" s3 v3 i, y, W" f1 X
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,) m; M- m- U/ P% G, u) b+ J) I" K) x
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
j+ o- j! L5 ?! r在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,* g# C8 V9 b$ H7 M) z8 |
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
6 u! o& G* k% a# x+ i因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
( ^6 p" _! V2 T( S栈区:
& v j0 z- K6 z& Z5 h/ U& H2 _
3 X! K9 o$ R5 N9 d|栈顶 低地址" F0 a. K W+ ]3 g
8 `) [0 s. y& E$ ^|第一个固定参数var1
: y; ?8 z& F4 p4 W|可变参数前的第一个固定参数var28 `+ |) i& O0 }# ^7 ^5 m
|可变参数的第一个参数# r, o1 _" v% f0 r' }) }7 P7 D: @1 d* V' o
|..., _# K0 k8 S0 v& o6 d* U/ n: ~) g
|可变参数的最后一个参数
2 ~3 _& P2 J. n V+ l: K|函数的倒数第二个固定参数var3
k" s) z: K8 @|函数的最后一个固定参数var4" X; m8 [& l* P( \
|...- g0 |; D/ Q; \4 B; q2 t; j9 I
|函数的返回地址# w2 G7 y) o/ k! [7 G
|...1 }. H+ j f, N9 Q7 A$ |
|栈底 高地址7 c4 L: p3 `% [' m- i6 n
u: x$ S* o& e+ T
2)使用可变参数所用到头文件和相关宏说明( m" S0 \0 Q G9 g
+ h7 E8 G) f: ?8 B* }7 F1 O& A) ]
在此,以TC2.0编译器为参考对象来说明./ l8 T' x- H0 b/ I- D% F
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
0 y1 Y7 y% y$ A* R3 E此文件为:
; Y- q3 Q/ h& i9 |: c/* stdarg.h, _) v( y8 v6 w* C G" m
2 M. l( K0 x& r. \2 w; t6 Y
Definitions for ACCESSing parameters in functions that accept4 B/ W! ]5 J, i$ G9 [
a variable number of arguments.
/ K7 ?4 C# [2 \! W3 A
4 g% e, G* S% q3 [' b \6 x8 z& eCopyright (c) Borland International 1987,1988
' m& {7 Q! x) M7 _/ @9 G4 n3 m. oAll Rights Reserved. V. p' R$ o; N* l7 }9 G
*/
4 V+ m8 G3 B7 M7 g: D4 ~. X#if __STDC__' p5 ^/ D u6 N
#define _Cdecl/ L. ?8 s' d% G' U
#else ^/ l Q( n. l, n! A- O
#define _Cdecl cdecl
: j- j4 d; y; }#endif
+ ^% i }% D% H) a0 {$ z$ e9 o& d3 T- f0 m% I9 g7 S
#if !defined(__STDARG)8 j- T& L" t9 r3 y- P! P, h" s
#define __STDARG
9 ]: e! ?; m2 l/ d2 C* v
+ s j6 {9 w0 k, }4 H' Q8 Dtypedef void *va_list;& s% P8 u) G1 p, m
: h6 U' g0 q( I. z& g#define va_start(ap, parmN) (ap = ...)/ _; u. B+ m& S; P, w" {
#define va_arg(ap, type) (*((type *)(ap))++)( t |5 R: ~$ `$ D8 @9 h- }
#define va_end(ap)
6 i3 p) Z5 r4 p6 E: H, i#define _va_ptr (...)
4 i- S ?) D! N9 @& A/ j, h#endif& N6 c) |- L: a+ L8 A, T) y
* g3 b1 N5 j) o* i
以上为"STDARG.H"的内容.. J3 M9 p1 t. }( Z9 C4 ?0 h7 O
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;0 b* m8 V) T/ z4 ~1 Q; g) k7 x5 Y
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list," ~- [# d% K* _+ F9 Z) M
parmN为可变参数的前面一个固定参数.0 G* I8 l$ ` E2 @
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.1 E7 W! A }9 {9 b1 i! U
va_end(ap) 结束可变参数获取.
, C/ t# b3 q. r, [1 t9 }( \
+ A$ j$ V8 r" n& Q* k+ l3)可变参数的使用实例9 @ r0 W4 Z" I$ e& {. K
+ Y; y: R! y3 s5 G实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
: W* R8 j4 H e& |. h8 ^$ ~6 e) D5 Q4 _6 J4 q0 Z
#include<stdio.h>) ^1 W& w; K# i1 B* }6 O
#include<conio.h>$ ^: R" s. Q% c2 \
#include<stdarg.h>
. b: y1 c4 W1 @3 A$ bvoid tVarArg(int num,...);/*num为可变参数的个数*/6 Q U/ S& G. E* o Q
int main(void)
/ c* E. t: v# ^7 T2 r{
3 V; {% u a4 N% _* fclrscr();
2 p. X1 E0 v3 Y, i0 r+ c- @tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");+ U7 F( S& s5 c7 y" P4 Z6 y) e
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
8 }* U- I$ t& V( a3 L8 s9 U5 vgetch();
. a) _$ v8 {% Hreturn 0;4 h/ }; Y/ w$ X, N$ }- N
}5 R" G! t6 U. L; u$ t" {! N x1 k
void tVarArg(int num,...), ?" k' ]/ r, j0 ?6 q) v
{
, j+ `6 X1 n3 q* @) w5 R0 T! `va_list argp; /*定义一个指向可变参数的变量*/
: _, M6 m" S0 B9 kva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/# V+ i% x, P8 W* I; x- Y, A* q
while(--num>=0)
+ f! Q u+ }' A printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,4 v/ L1 {% v: Y
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/) J3 j6 O: H; H. |
va_end(argp); /*结束可变参数获取*/
* ?& Q4 O, {# t1 ?' Xreturn ;" o( r9 i* l9 Q9 y+ R2 H* P
}
- a7 z) D$ C" U R: q
C8 Z6 E( Q: U, `9 h* u4)可变参数的使用需要注意的问题
# _) D& K5 n, I. ~% T
8 ] H5 Y& Y- X& a; Y4 y u) r1.每个函数的可变参数至多有一个.
0 R6 E! N: m. a7 m, O( m- K1 H* `2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.( d5 B5 w) ^; s5 a# h, r
3.可变参数的个数不确定,完全由程序约定.
6 Y7 z$ h& }( B4 N8 S! G* m: G4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.) u+ @* O+ l! b7 p, {- N
而printf()中不是实现了识别参数吗?那是因为函数
. J/ y8 z. Z- v3 }/ z4 h$ xprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 9 X) T9 x8 N+ y% j5 t; Z$ F! L5 H
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ( Q6 l" w8 W& I) A
过在自己的程序里作判断来实现的.
% w& [* C# \- U0 P5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|