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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
5 N }1 I( X ?9 v' L$ i: _" h* m它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.3 A8 F' ~- s$ }' y) `( y* Z
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不' j6 y8 q: G+ ? Q/ e
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有2 A( N) p! @: k* M& [& X2 k
实际的名称与之相对应. g$ w7 c# X$ p/ V% X! Q& V
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
- s: j" t" W" ~2 T3 o然而,更多地自由,同样也加大操作上的难度.# @' l7 \5 D Q( R
以下就对可变参数的几个方面作一定的介绍.1 t- U3 m3 J9 X
; [4 w% Y' o3 \/ }0 F
1)可变参数的存储形式.& Z# s/ L9 ]# @8 ~4 [
& E! E# o; t. _: Z( h8 \大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,$ G- L: G1 {+ }: y. I0 H
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
! Z* {- u3 p0 ?- M/ y! m在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,3 u$ L( {1 A$ }3 |$ l& Z
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存. J u; g$ e: f% d
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):' b; `! _1 X% y' i
栈区: J8 [1 p! e; [) }
3 J r, ~' t: P: F% e|栈顶 低地址
$ D( e/ }! Y% t5 v6 y
6 W6 V6 F. ]2 R% l5 O|第一个固定参数var14 O: u" H6 I8 h) K/ e+ l- G
|可变参数前的第一个固定参数var2
3 Q9 A; e: n2 ]: e' s# [6 s6 n! e|可变参数的第一个参数" C9 G+ l3 _9 q1 X0 P7 J% `
|...: c6 X. f: Z! ^4 s7 B% H
|可变参数的最后一个参数) T' I$ o# c2 q1 K% [& X2 v
|函数的倒数第二个固定参数var3 ]" n7 Q9 D3 ?: J
|函数的最后一个固定参数var4
! u7 m3 |" O2 ]1 A1 {, N/ O; e8 W|...
# f0 B% M& @$ w( L* X|函数的返回地址6 s* W+ R# k5 m4 [4 _4 Z+ y& _
|.... n- Y9 ^: N: L" g+ O! ?
|栈底 高地址
8 g7 O+ S' [; B6 d, V/ W" E5 k+ }3 ]2 I, R! \( w8 @# J
2)使用可变参数所用到头文件和相关宏说明
: @$ y% Z; t/ T0 n
" Q' L: l- D$ p: K( S7 Y- v3 l在此,以TC2.0编译器为参考对象来说明.
: d/ p: n; P% D3 j/ t5 x可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中., ?, q, D& S, U/ M
此文件为:# r% q$ }- d) o( P2 t
/* stdarg.h
/ [0 c" M3 u: q* H+ f/ p/ P! j9 t7 t" i0 P, q, p# U, P
Definitions for ACCESSing parameters in functions that accept# ]$ E# c. b1 q$ q& x' u' v4 Y
a variable number of arguments.1 o0 }7 A0 h2 ^- j; @' ]
! S) z' e8 r9 ~5 f. ^$ pCopyright (c) Borland International 1987,1988
* N3 D% _$ D* a# _! LAll Rights Reserved.
+ V8 r! @# Z1 \( z' z) P*/. L( n) R0 q3 Q
#if __STDC__
3 m( f' V2 o$ r! H( I9 w: Z#define _Cdecl
$ f" Y) v' T( J2 ?% I#else% p4 s: \& B3 M" n1 k( `+ V3 g9 p
#define _Cdecl cdecl
5 o$ S; ~4 l( n/ @#endif3 z8 B6 g6 F3 e9 C8 l
9 X( @* G5 ^1 |" e9 W; [
#if !defined(__STDARG)6 Q3 Y( t9 k, r+ ^- q2 X
#define __STDARG) v) |3 |# d/ u/ J! F' p
1 `0 [4 T; {6 J/ f9 _) l
typedef void *va_list;
( k/ V3 x$ A+ c$ I3 g
: {( u6 ~2 _: a' v/ |#define va_start(ap, parmN) (ap = ...)
# M' U9 q: J$ x4 w( x& E9 e! \6 \#define va_arg(ap, type) (*((type *)(ap))++)7 a& r" r* |: l* `. _4 l( R
#define va_end(ap)+ v3 T& \5 C2 r7 W
#define _va_ptr (...), l x3 R/ o; y, l' ?2 _9 q
#endif6 Q* ~1 j% L( M* D* c: j
( Z$ g1 J9 H8 h% t
以上为"STDARG.H"的内容.
2 h" T5 c2 M) }, b, q" k- z6 @) n该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;' G5 E: s( V z2 ^
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
! x5 q9 w: T# T) w' _0 KparmN为可变参数的前面一个固定参数.2 C' a4 S+ S2 V% M* K/ y3 l
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.! _6 T8 S; m2 }& V! a
va_end(ap) 结束可变参数获取.) P9 l: o& q' \/ X* H
/ j7 m7 G b; x r. V
3)可变参数的使用实例- p. G+ C) c7 m
& }4 J0 T6 _ ] O- y( i实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.4 R: q5 r& k/ m9 B# ^& t
( B; u6 B) D4 F) t% r( V* z
#include<stdio.h>! [7 m. P$ u$ o$ V. _
#include<conio.h>
' _* ?& ~* g4 i8 }; k0 |9 M#include<stdarg.h>
- D7 [- O9 V5 c/ d) o( ~void tVarArg(int num,...);/*num为可变参数的个数*/9 @2 m% ?6 ]; J( W. j, B% x
int main(void)4 M" r) t3 {& k0 @2 }8 I% s' C5 {
{
; E( a h* i9 R, ^clrscr();, H) s7 H& z1 J, b4 C
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
3 f8 n+ K" m. D8 H% o# W1 ftVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");7 ?! d4 B' i0 q; c0 B( L
getch(); r" M5 s) Q8 z8 X/ T$ ^
return 0;
m# B! T/ ~4 n" p( l' X7 {9 y: t}& |1 Y/ q, Y8 }' W# h# A. L) h
void tVarArg(int num,...)6 F( b1 ?. d) D. N7 V8 a
{
! Y5 `1 o% J+ n4 ]9 M# A8 ~% o. gva_list argp; /*定义一个指向可变参数的变量*/7 Z q& |% _4 J q( ]! Q% X) {
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
$ o( i3 i, N9 Q4 P2 }9 iwhile(--num>=0). J" S$ j9 L8 C) h$ `/ D x
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,' z- r! ^, `4 m5 U5 ?
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
# Q$ `2 D% ?& y: C' J) b$ ?va_end(argp); /*结束可变参数获取*/
( v- P# Q6 M* `! N- p/ Z3 ereturn ;8 N( y3 U i3 ^
}1 l( B3 A/ Y& t2 v
6 p7 [* b/ C" r m: w# f& w
4)可变参数的使用需要注意的问题6 y7 ?' ?# G0 u) t
# }3 ^4 x6 n) t# j; r
1.每个函数的可变参数至多有一个.
1 i" @3 ~; K: Y( H# j- k2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
* W1 i6 S& w2 b# G2 T8 c) W3.可变参数的个数不确定,完全由程序约定.$ P: m5 h: z. X. x0 P4 k& D
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
9 K1 A9 R/ F c7 Q/ M而printf()中不是实现了识别参数吗?那是因为函数
' V- ~; E! a4 q( t% s7 S0 \printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
' F7 [2 D% `* \$ Z的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 3 S! ]) x4 ^/ }# _! r. X V
过在自己的程序里作判断来实现的. ( a4 u( n! T5 e6 O6 s
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|