获得本站免费赞助空间请点这里
返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.
0 g  ^, V; U* Q) B) R9 A它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
4 T; @/ E' Y% g7 b- N% y& H可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不9 E8 ?8 u5 E4 x; v' o" ]
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有0 E/ \4 b3 q0 |: }- q0 K; A* K# ^% e
实际的名称与之相对应.
: L2 X- \' i; L) O' d, s  c* G由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
6 J6 b" B1 @3 e, M0 o然而,更多地自由,同样也加大操作上的难度.
* X9 Y- \8 n6 u+ F# p以下就对可变参数的几个方面作一定的介绍.
3 C" C5 c2 K" H) W; k8 }; g$ F$ L6 p' k, ]: O% ]/ q
1)可变参数的存储形式.+ y9 u! ^( }; `9 {8 P- `

; X! W$ L/ h( y2 o; v大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,* X& W8 J" i9 X2 q+ {6 n
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.  U$ r0 N. I8 C# z7 T* I  U
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
! D/ N$ e' n2 M: G; j这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
$ m/ N+ v5 ]+ P+ B: J( v因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):2 I% W+ p. l& E$ Y2 r) ]
栈区:0 l: ^" k7 ~- d/ r: p; \5 b
$ f9 R1 G' j# V$ m7 P
|栈顶             低地址
; _* D' V" r. v# X$ O* T4 _6 _4 T& t2 f/ B. R% \+ S" C! L  R# L
|第一个固定参数var1$ S. U! G& h& i4 J9 Q
|可变参数前的第一个固定参数var2
+ a2 }: R% A% T1 p7 W# p9 @& z# Q2 c|可变参数的第一个参数
  B$ g: u/ Z7 r|...
  x1 G0 d3 L8 a+ E|可变参数的最后一个参数7 P( K* Q* V: b3 Y$ G) K* {8 o' {
|函数的倒数第二个固定参数var3: B" R- ^2 m/ e2 T+ P( j2 h, z
|函数的最后一个固定参数var40 s2 ]( j& y/ D: ]. x" s
|...
$ i$ `- j3 W& u|函数的返回地址
; U) s" R8 S7 V" d/ Q4 L( L* C: X|...* J! R& k8 H/ r' J: L. B
|栈底    高地址9 n$ y6 G4 b% V" n) A
9 N' P- X& s- `1 U3 M+ I  G
2)使用可变参数所用到头文件和相关宏说明4 _# ^, \! q: o% n- s* f+ v

/ H8 }' K+ q# b7 ]/ y+ e在此,以TC2.0编译器为参考对象来说明.
4 w! `3 g; U" P1 M% m( b# a可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.+ s: r2 T7 p6 e
此文件为:
; o0 f; b9 z3 W( @, C2 |% N/* stdarg.h% ?- Q8 i# K4 v0 }, Q, f0 {

( G/ l" c! X5 @2 {3 [3 U' j* VDefinitions for ACCESSing parameters in functions that accept8 s% H7 h  p2 Y0 Q
a variable number of arguments.
' G9 O( D, A9 I1 [! Y
; M; x. p1 Y2 o# F, rCopyright (c) Borland International 1987,1988
8 ?% @' j. u  d+ P: iAll Rights Reserved.
8 b8 a* q6 ^$ w0 z9 d*/
' n* B% C3 o* c1 N3 b#if __STDC__
) X+ m& L5 N2 \7 `0 [! t#define _Cdecl
9 D3 M" C2 {" L#else
0 Q, N0 n% j8 f  F8 a. E, [#define _Cdecl cdecl! z! s$ E5 ^. `; y) r9 P& Q
#endif  H- s4 d8 ^" _2 s# _

2 l% c: q: F8 g5 ]- h#if !defined(__STDARG)+ O3 y; V. \# |3 r% Y
#define __STDARG' W. n% v" J1 A; J) B
* f" U8 s& K) V5 n. S
typedef void *va_list;7 ~. i) @/ o* K8 }6 @! X2 c8 r

9 a: ]7 X) F5 s#define va_start(ap, parmN) (ap = ...)
4 B9 [1 V4 V6 f) i- A9 y/ Q0 J7 c#define va_arg(ap, type) (*((type *)(ap))++)1 ~3 B" x9 Q# O7 D1 v
#define va_end(ap)# z9 k& e/ c$ p  _0 C1 {9 x7 g
#define _va_ptr   (...)
) h  P1 F4 y1 C9 k#endif
& Y0 W% s* Z: x: I! V: k- ?1 Z3 j3 f+ a' a0 y0 }  l! p! Z  r
以上为"STDARG.H"的内容., q+ w2 m9 P' P9 r2 E
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
  g& ~2 P6 |* w: p$ v0 a; S, |va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,0 }1 |, u9 ~. k% O" P+ S1 [
parmN为可变参数的前面一个固定参数.* U5 m! d1 C2 Y/ }1 g, |! O  G
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
0 v/ e6 L5 c3 p: W% _va_end(ap) 结束可变参数获取.
6 @- V- z' s6 J% j+ P( d: l
4 k/ G9 H$ \3 o3)可变参数的使用实例* h: h: R( V: a2 g2 z

  B& |* }7 y/ w+ I3 ^0 |实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.& i- I  L! M+ B3 z/ j- d
3 G4 [; Q% O% F4 R+ z# Z% E
#include<stdio.h>
+ z' E2 ]9 v* J8 g' m5 j. O#include<conio.h># n/ _2 Z; y5 s
#include<stdarg.h>
% U' s) T* {6 ^! M- Yvoid tVarArg(int num,...);/*num为可变参数的个数*/; s, K5 `; q- w3 i/ u
int main(void)
9 H, E  q4 v9 c2 R! z, l{
0 v! ?+ t( y. dclrscr();
0 j3 J1 U4 N- A6 Q% E  c! [" VtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
% N0 L; t; M- NtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
. D1 H1 @) X9 s9 D4 lgetch();/ N; X; A9 _) p2 x4 {
return 0;- W% H9 g, q1 `0 |5 q. u  d
}
( J" ?; O6 g/ H; p2 L4 ivoid tVarArg(int num,...)
0 F7 K2 x6 y7 }9 T5 ?{
. q) G- y/ c5 H$ S5 I. U+ z1 G/ ~va_list argp;  /*定义一个指向可变参数的变量*/
& `7 y, |" _- t# Tva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
! d1 k6 [6 \$ K4 [3 V- F- Lwhile(--num>=0), a2 s4 c* D% f: |; Q2 F9 v7 h
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,8 k+ }4 U* l6 I; s# K" `
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
# M7 [" R5 s! M' A4 ~, L# Iva_end(argp);  /*结束可变参数获取*/0 x( V- z# J2 e( A- t" w
return ;
. [1 j. q3 w- Z8 G5 v- Q}; v1 Z* ]; D2 L) `& b
$ ^4 ^& ~5 a! T/ B! [$ `
4)可变参数的使用需要注意的问题
* k8 r* W& X7 k& b1 m/ e) w% Y. U5 Q/ i: j
1.每个函数的可变参数至多有一个.; Z: p* W3 Z3 l0 ^9 t+ u
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.5 O% O+ d1 h+ A
3.可变参数的个数不确定,完全由程序约定.
: X2 B- G% ?6 N3 i4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.3 Y) T% A: l! [' x* U
而printf()中不是实现了识别参数吗?那是因为函数   ]" t# f) [9 `
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 3 y' b; ^8 W4 }# \
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 * _3 H0 o. }( `( R2 y4 i# Z, ^1 \
过在自己的程序里作判断来实现的. ; u; M* r7 U  M! r2 N& a7 z2 _
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

返回列表
【捌玖网络】已经运行: