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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
5 h8 U. j1 Z# s3 o! A9 ~9 U. T0 k0 {它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.% y! l; I7 @+ j+ M1 o( \
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
0 p# b! g) I; w定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
3 ~% U2 k+ k. D. g实际的名称与之相对应.9 m; b! r. f& m" `
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.7 w \4 y! h ~: v6 x3 W( ^7 v
然而,更多地自由,同样也加大操作上的难度.7 W; I; [ o6 ?; C+ O! d5 g$ T
以下就对可变参数的几个方面作一定的介绍.
1 Y0 o3 y. G) `6 ]
Z- L2 i5 Y0 o! [1 E7 G. p. @1)可变参数的存储形式.& K$ D' R* }0 F: z& C; E
" v4 \1 C1 p5 D0 [& g1 ]大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
" t, K y; K l存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
3 F3 d6 z z9 {( d! |- l" F在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,$ y& U; Q+ Y+ T' F; L- K1 v
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
0 g# m9 m2 F& n" W" f因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
' ]+ N5 p+ @5 ^( H栈区:( X8 b* j- u. {3 t! y6 }$ d N1 M
* w8 A- P' b4 V' q; I8 o
|栈顶 低地址
8 n1 Y. V# s: q/ W5 A; l3 h2 ~7 p! j% K' h' `8 T' g6 T6 _5 Z+ c
|第一个固定参数var1
4 V: J4 R4 L& @4 H; }' f! O|可变参数前的第一个固定参数var2
, K' Z* T y0 N/ Q. n|可变参数的第一个参数6 H" I1 J, d+ m2 O
|...
# _& P, c5 F, r6 R|可变参数的最后一个参数
: x7 K# W/ J% _|函数的倒数第二个固定参数var3" k" X% m4 t$ B( ?, Q; m
|函数的最后一个固定参数var4
9 I$ i: t" N1 l M8 Q) I4 ?|...
9 u5 v0 @* m/ ?9 y( f9 A0 {|函数的返回地址6 Z) z+ a$ ~7 z2 ^# i$ o! t! o/ B
|...9 z: H: F: K0 B6 _0 u" {$ \% }
|栈底 高地址
/ s: b& o2 x5 x# I. w: L5 E- E$ L( T4 i
2)使用可变参数所用到头文件和相关宏说明8 L- s5 U% a9 B) B. P( k
1 g$ I! t2 G4 B6 v4 ?; P: G, W2 ^
在此,以TC2.0编译器为参考对象来说明.
0 V! k0 g( Y$ Z- V% K+ E可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.$ {; C! j5 Q. x- ^" X
此文件为:
' P# \+ A% Q# {! r$ @/* stdarg.h. D5 O" w- R& s2 S: ]# F
8 W) w7 |1 R6 O0 D% g8 j9 R+ YDefinitions for ACCESSing parameters in functions that accept3 A5 _ ]5 b! f; e& @9 I3 c
a variable number of arguments.
8 [$ z, A& A G5 u3 i* B
" X7 a% Y V# k# p+ GCopyright (c) Borland International 1987,1988
- y) X6 b- _* lAll Rights Reserved.
9 e9 b$ d: Q! q*/$ j R' V) }! I. I+ d
#if __STDC__
9 }' F, `! u2 D- x& Y& Q" A0 u#define _Cdecl
6 z1 \( w1 H& T" F#else4 _6 _ x' t- @* B. A5 L
#define _Cdecl cdecl% `7 C; p- X; ^" G
#endif1 _8 i, z+ F7 j5 u1 i
2 S$ j: Z5 y. B! K8 f( n#if !defined(__STDARG)7 |6 r9 ^8 n: z% B7 {
#define __STDARG! U. m8 J! a& `" o# R) Y5 C& l
- T0 |8 ]4 w2 r0 U* }typedef void *va_list;
; J Z# Y& Q( g: ?$ {, T5 {2 I3 b7 w' O% J/ q$ ~4 C$ p. L3 T+ Y
#define va_start(ap, parmN) (ap = ...)
1 f% `6 x7 m- S2 N( g#define va_arg(ap, type) (*((type *)(ap))++)
8 [' C6 p" I% v+ n$ ]#define va_end(ap)
3 b, ^9 K7 c' W$ ~$ y#define _va_ptr (...)
8 V, \( H2 K, A#endif
, B b4 s' D" ?# |
0 K* z( ~, |, C以上为"STDARG.H"的内容." j' b5 o( y) Y% {! m
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;/ L6 g" W; v& N) ~7 o- r
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,! ~" R7 x4 Q* [& P; ~& ~
parmN为可变参数的前面一个固定参数.
0 m$ k: J. @2 wva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
, u) \0 @* o0 ^4 [8 o0 Y5 Pva_end(ap) 结束可变参数获取.
$ G- m9 D0 t9 z) W- b* C: A F( Z
% U7 K9 M# o, v" T) _3)可变参数的使用实例
! h) ^; \1 V* A& _, S$ |! o0 d/ Q
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.. {' V6 Y3 |3 R8 k9 {
+ t0 ^" O7 P, U/ f) u
#include<stdio.h>
! q) L+ q Z* D4 [0 G: U7 A# P' n8 Y, }#include<conio.h>8 s R8 R: a2 A- ^
#include<stdarg.h>
9 _- m3 _% R) M# g3 O- Q+ h# [void tVarArg(int num,...);/*num为可变参数的个数*/
1 } H5 _9 r- ~3 E2 X- e. Tint main(void)) t8 H% P1 i- l/ Q( W( T
{
% y( ^! f1 T: g" u+ v4 q$ hclrscr();
! e& z8 g' _" B; l4 x3 e! S# t& EtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
% `& p O# s: P% O+ ^1 ztVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
* m8 ~/ e. y) C( T: I7 V1 Y4 U2 vgetch();
6 s2 p1 _7 g8 o6 breturn 0;5 _4 L& u/ R$ F/ d% a3 ]; E
}
* N5 Q$ ^5 d/ dvoid tVarArg(int num,...)
/ [+ f" Z/ Y# w4 j$ q{% r8 T' q' j; w$ k+ _
va_list argp; /*定义一个指向可变参数的变量*/
& S; q* L8 n( \- z8 ^7 nva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
' t4 s: o/ P$ c( s" }while(--num>=0)
0 ^9 e6 i/ e6 Q/ u: x printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
! @; l' X7 c( [4 n' x* V 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/: c7 L$ k( g; h& F* a( Y/ e! B" {
va_end(argp); /*结束可变参数获取*/
& N: ~2 }+ O h8 \3 nreturn ;
, ?, _# D/ X' G- z! c; d& b* u7 {}8 z- n- `- q& m) p' K9 X
2 n1 f- m5 k3 P3 c% M
4)可变参数的使用需要注意的问题
7 k$ X2 i8 N8 O: T/ h: m: {
+ k! a" C" t; \! |" d- d7 P- ~5 o1.每个函数的可变参数至多有一个.
5 h1 y8 Y) E7 a- E/ }2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
$ C% h2 ^; T. W* \5 I3.可变参数的个数不确定,完全由程序约定.
; u/ W# ^9 X! o& C4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.) @5 j, M9 A1 h6 t& Y! q
而printf()中不是实现了识别参数吗?那是因为函数 ( u) o1 x$ u+ z- a( C
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 3 Q; M7 u# j" K5 W: r+ i& B
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ) \$ E4 z6 O: p9 T* W
过在自己的程序里作判断来实现的.
: O) [9 U2 o& Z, O* y* |5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|