Board logo

标题: 函数的可变参数详谈 [打印本页]

作者: zw2004    时间: 2008-1-21 19:42     标题: 函数的可变参数详谈

可变参数的英文表示为:variable argument.
7 e7 y$ u4 [9 k$ N5 Q( H它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.6 \! I. V  b" K! J. t
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不. m  b2 x  ]4 I% H3 Y6 h. [8 m/ _
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
) f( c5 m2 |% V9 z实际的名称与之相对应.
7 Y2 W* y# j8 m6 F7 r由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
1 d& o$ y. F, I) E4 Y5 a然而,更多地自由,同样也加大操作上的难度.: y& \, v5 [/ f0 ~
以下就对可变参数的几个方面作一定的介绍.
# p" }: p% F) _: O6 w/ A
0 M6 k  V1 O3 E. ]$ ~2 q) t+ u1)可变参数的存储形式.
8 e# P- {& z) M& T4 R
& G+ [2 p6 G# i8 D; C大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,( O+ y9 k- i/ r; s' c# N2 L! R
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
9 Z0 z1 c$ N8 l' F6 N在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,, O3 I; r8 j1 C' K
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.$ Z! T- Y; M; h3 L) Y- n, s; ?
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):% {- z3 W/ G7 [* o: }
栈区:
! p/ F7 [8 ?" [) s9 L/ m7 M; T  p/ Y7 y+ J) H0 O% d0 r
|栈顶             低地址% V5 P. ^6 P% Z/ x1 J7 @
1 E: l4 k! G! G, T' H9 l! l: Y
|第一个固定参数var1
' F/ c* s3 |7 N8 J0 q|可变参数前的第一个固定参数var2
6 X& C' U& J' S! c|可变参数的第一个参数
! H4 S+ E9 |. j+ f0 Z8 r) f4 d* `|...
1 B+ B: J/ q0 W* ?' F|可变参数的最后一个参数- B3 i- D6 M! [
|函数的倒数第二个固定参数var3
2 X/ u' s) _, s2 X|函数的最后一个固定参数var4
5 M$ o5 Z6 R& R. B$ f|...) U/ c% `* \- r+ L/ Y
|函数的返回地址
  Z" O0 `* k: q% X2 q  p6 }" \% w. N' G|...
+ n% f8 h0 A- r3 f|栈底    高地址9 F* k3 h& b3 L0 \

. G' @2 |, g( F! M2)使用可变参数所用到头文件和相关宏说明
5 c9 ~; n, x; r" e$ u. e+ h9 |
- t) I: |2 u2 e& i$ C在此,以TC2.0编译器为参考对象来说明.
- n% i% h) Q+ Q, ?可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.0 q& r& C& K+ H7 I+ W
此文件为:$ d! O' o$ M5 ~7 A1 f3 C* V
/* stdarg.h# s/ Q2 z. ^# Z; \

0 i6 ~3 \! t2 e# L$ q  vDefinitions for ACCESSing parameters in functions that accept
0 ]" V! i1 H8 z& ca variable number of arguments.3 F9 C  l2 n2 v: t( E
8 f$ Y: X1 o: l7 ^
Copyright (c) Borland International 1987,1988
7 S" M! C/ g6 uAll Rights Reserved.
- L9 Y( n5 K) H  A. M/ i$ t*/
1 v3 M; `% w! i#if __STDC__
3 B) S! u$ c4 q+ F; n. L  z$ s#define _Cdecl
& W6 G+ H1 Y+ G. S# g#else& x6 F' I# l' p4 G" a4 m
#define _Cdecl cdecl) x5 ~7 z* i. u% u* R; x6 `
#endif% V; o5 B6 e) m$ a% q" U% v8 N
) R, x: A& ~! b! S
#if !defined(__STDARG)
" v) p- T+ C7 Z+ F+ ^7 a0 _3 o#define __STDARG/ t0 {; I% x1 ?3 m

- ?1 F$ |, R2 l5 L6 t& A: htypedef void *va_list;
& l, ?; b" a/ Q: E+ T
9 J: b, }2 @! Q8 g# p( Y#define va_start(ap, parmN) (ap = ...)
: \7 M0 P$ `/ p: j' P3 t#define va_arg(ap, type) (*((type *)(ap))++)/ `$ F2 G# p( A) h
#define va_end(ap), i: x+ p3 B8 L% L! C
#define _va_ptr   (...)9 K& A: k+ r8 y1 L& a
#endif
4 Y: m; R- R4 G7 k! p/ Q7 l5 d" m5 B! ]( y& o
以上为"STDARG.H"的内容.0 _9 }3 i% ^: l/ T. i. o
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;5 p/ M4 q- G( q6 |
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,* s9 W5 {- r% e4 _
parmN为可变参数的前面一个固定参数.0 s: ^. `; R; A" ?/ [, g
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.1 @9 |. O/ C$ P' v0 M4 B
va_end(ap) 结束可变参数获取.
- T+ y1 L, R# i1 j2 L: p% Q  ?  m  _; N% h3 z
3)可变参数的使用实例2 m$ O3 o# p  o2 s2 H1 c$ J
6 x8 V) @! k7 b3 k, b4 K5 A
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
% J+ a! y$ d( ?4 T  v3 O! |+ L7 |7 {* E# a% q
#include<stdio.h>
) `) q: u4 F, V5 z#include<conio.h>
5 a/ c$ I6 s7 W( X( p- [#include<stdarg.h>
# v* f9 E* D, Avoid tVarArg(int num,...);/*num为可变参数的个数*// h3 `/ V' G2 K* E  s8 G6 E
int main(void)* z! F' D( \$ V4 A2 Q+ |
{1 N) k* E) I: o% N+ e
clrscr();
- h( W4 b0 m+ OtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
9 b( C1 r, P' y( |& [tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");6 K1 l3 s& m0 J# \1 K3 V" k
getch();
" s/ F  p' v& [6 H1 t, Kreturn 0;$ {0 z( J9 |0 u* X4 [& @% ^
}: ?6 s* X# o! d8 z9 q8 m5 C
void tVarArg(int num,...). X% y7 Q# a) F2 |0 c
{
) E$ l" t! [+ e' H. Vva_list argp;  /*定义一个指向可变参数的变量*/
2 }2 y% i" O/ ]va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*// i2 H3 U0 D) P, g
while(--num>=0)1 [! c& @7 {, g2 E- }1 {1 S
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,- C7 ^( Y7 ]% f  p) e9 O
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
# h& v% z. w( t9 e, fva_end(argp);  /*结束可变参数获取*/
. g- ~/ |. [; J4 yreturn ;6 R; O3 k' @0 P7 P
}
( n- G3 v" {9 }: N: _% T: c
* [3 s9 L9 H2 L6 g; \4)可变参数的使用需要注意的问题0 {0 @2 S! ~7 U. L" [

; I# |- [4 l! o' H$ D: ~1.每个函数的可变参数至多有一个.2 N& J& p, [) ^# Y& Z5 L! I
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.' |% _1 e( s" V" A5 }4 }
3.可变参数的个数不确定,完全由程序约定./ l3 d+ t2 r: G4 D, B9 t/ X* w
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换./ x3 G9 f' x- N( `3 `- W8 F3 I
而printf()中不是实现了识别参数吗?那是因为函数
# N* d9 D) x4 M% e  ^printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg $ R; W5 p* \$ |( Q
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
. a  L+ R( ?2 k, H0 q过在自己的程序里作判断来实现的.
$ U1 |# b' g, b. y# l! T5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




欢迎光临 捌玖网络工作室 (http://89w.org/) Powered by Discuz! 7.2