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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.5 k* I6 Q4 Q# _# c9 a
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.* t4 a! B8 G) M
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
& b* {, ]' E4 y7 C" \% v v定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有' y$ s( P( {8 v: m0 r" S+ r
实际的名称与之相对应.
& C3 z, ]$ E4 p# }, w1 n( l由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
' V7 R) R5 C, p$ X3 x, e然而,更多地自由,同样也加大操作上的难度.
' Z0 ~2 E" E3 n6 {" ^; j! N以下就对可变参数的几个方面作一定的介绍.
$ B# b* L! J( z) |
6 j; y o8 n- P6 {! I2 `5 P L1)可变参数的存储形式./ b( Z! ]( P* u" o8 Z+ }
. D8 U8 g' V, G. h+ f/ _
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
8 q+ K# |# C: i3 _. C1 \' G0 o' G存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
. R3 i* Q0 H' t: S8 l, Y2 X& b在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,. w5 l4 w) J9 [+ s1 |
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.' M+ u" \% F9 `5 p6 U2 _
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
8 g }* x& Y& ?) \6 i1 c栈区:9 C- l9 f# U, e, p! ~: Z3 Q2 M
7 P9 P3 w6 I9 l7 @: P! i- G) _: l|栈顶 低地址
! d0 v1 d/ X0 A" P# T, I" q. s- G" W* e
|第一个固定参数var1. t2 V5 G. @$ T- d
|可变参数前的第一个固定参数var2
7 n( C' [$ p4 O3 U+ {9 O% \|可变参数的第一个参数7 Z" |! y4 |+ j$ g9 W
|...* w7 D K# A ~- V+ d# K
|可变参数的最后一个参数
% ~9 b) z$ d, ^|函数的倒数第二个固定参数var37 M' q8 L( `7 M
|函数的最后一个固定参数var4
! W3 V' W" U f2 }2 q/ T% [4 \4 f' o|...5 ]- q* c+ a7 H% ?3 C7 F
|函数的返回地址) Z/ y4 B( T% Q
|...8 u( t3 M9 z& i: b! Z
|栈底 高地址6 P; S L8 v4 i: b& n' I, b/ w$ e
7 U* F0 q+ A, Q2)使用可变参数所用到头文件和相关宏说明! K7 d/ L2 x# Y: o6 h2 }' e* [. q, ^3 C! f
& U# Q. f8 l5 C5 Z) |" F6 n+ a& H在此,以TC2.0编译器为参考对象来说明.8 t: {0 K' p O7 o2 A
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.+ K$ I3 l1 ?* l2 A/ {
此文件为:/ ?5 E9 D: @0 p" t
/* stdarg.h
0 p" q9 F6 c8 b9 z4 y2 B1 W, Y- @5 E9 |" c1 e9 n/ m# U- q" t
Definitions for ACCESSing parameters in functions that accept
8 F+ [2 }- g& S9 f m" w o, d/ Ea variable number of arguments.+ x6 t% X# b) p
. A9 K$ Q& ?& `$ i, M6 t: kCopyright (c) Borland International 1987,1988) l& C9 l6 ^8 K5 G* U3 B* u' O
All Rights Reserved.
& N" u- }+ a5 L4 ~, J*/
. w# M8 k# K* E6 P, s! u I#if __STDC__) u1 B2 L7 D2 F6 K- c
#define _Cdecl
g: N2 C0 d2 t8 D0 I#else
; c4 c( d! Z- N, C* ^#define _Cdecl cdecl0 t6 P4 M, D5 { r
#endif
, z" B! I7 G9 ~. I% C* ] W) `, u
5 C! ~4 T$ B+ P( G5 U& u. t#if !defined(__STDARG)" c$ [- P$ Y8 i h, X# e0 X
#define __STDARG
/ ?1 i+ w: z$ {3 B; h2 J: d7 {2 n8 E/ E1 [
typedef void *va_list;
! \$ p# h- h g1 [3 V! R4 I0 c' j, H" z2 A+ B0 x$ s0 i
#define va_start(ap, parmN) (ap = ...)0 F( ~' v& N/ v: C7 f4 e9 I* |
#define va_arg(ap, type) (*((type *)(ap))++)
2 w3 g( e: l& [4 O#define va_end(ap)+ j f0 P0 v' J* k& o2 Z
#define _va_ptr (...)1 M, l8 Z _: x* S9 I/ M2 |$ @
#endif
* M4 c. W7 S- }9 `- L8 {- A/ ?3 x3 t2 ^$ Z2 R
以上为"STDARG.H"的内容.* b( s* Q* _+ y# X( R
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;2 X1 Q, |" M& v, T
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
- L: Y6 @; ^5 J) a2 YparmN为可变参数的前面一个固定参数. B0 P/ K z$ ~7 x
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
2 X1 w) Q0 y6 ^) X9 Mva_end(ap) 结束可变参数获取.
/ B7 f4 ?& J5 C) O. l" J% I7 w* R% `
2 R* r- p2 x, z! M9 D4 `3)可变参数的使用实例
, f @& C: v. M( i. I7 C8 G& m* K% ?! S# M4 u3 k
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
: k9 r7 g$ V/ \' s5 i0 X7 s
6 j$ o* n8 a8 L8 ~7 W#include<stdio.h>
0 o3 I+ {" r7 G* K#include<conio.h>& y1 `7 h. y: D0 H) ~, ^& S) z
#include<stdarg.h>
7 p% y* Z/ U% H4 _& C/ f. Gvoid tVarArg(int num,...);/*num为可变参数的个数*/
4 V" z9 n1 ~2 z% sint main(void)( _" w: J( r5 A2 g/ R5 d+ b
{8 g2 o. C, T- @! [6 h
clrscr();
, [. R5 M% E. ~, V. a: w, p% KtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
# R: Q& I% V$ U( c* i' a2 _6 ^tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
" h& a- i2 S5 n8 u$ sgetch();5 P/ w- [0 D4 K) d
return 0;
$ J6 q( c s: V2 t. I" r}
+ N( g7 T2 }/ `" x% `' m- v9 q1 q9 X+ Wvoid tVarArg(int num,...)8 X1 z! C8 D4 {" S- c _
{7 L. ]. U! k: Z8 o; d- s
va_list argp; /*定义一个指向可变参数的变量*/
: T5 @+ \2 M% a. s2 y. bva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/8 N! O1 z4 D* j+ V7 K0 `. |
while(--num>=0)3 h/ g f* _9 l7 s& T
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,4 V3 P V! @, }: ~* N( {
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/8 Q6 f7 c2 A; |" ]% j3 v; ?
va_end(argp); /*结束可变参数获取*/
9 u% q9 X5 y& Zreturn ;
, T( U" ?5 |0 D9 { N' m+ l}
( V0 T3 Y0 A' h2 {8 D& P7 M3 `
. i \* f+ O" `$ c' M2 p4)可变参数的使用需要注意的问题2 g& u6 c6 @1 H. O- F! I7 G; ~0 p% I; r
( @! m' i+ K( ~1.每个函数的可变参数至多有一个.8 E t- u) q3 B; f1 K6 y3 S
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.4 z0 ]+ B3 N3 x# y9 Q7 I( r' f& G
3.可变参数的个数不确定,完全由程序约定.6 R& o* w% ?# O
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
2 G& |+ y2 ~! n" S D8 ^$ G6 ^而printf()中不是实现了识别参数吗?那是因为函数 ( d- a3 T5 e8 K# p% L g
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
U/ J1 x) c1 h& N; e的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 + h# m5 |; Y0 i& h* e7 Z: @
过在自己的程序里作判断来实现的. ! d1 q' f- D- Y
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|