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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
3 |1 a+ m$ \( E: C" b$ Y# D它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.' n* s$ E+ j/ {6 X/ y! X9 b* f
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不 a# y Y8 F; q8 [1 i" F
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
& i7 ?2 j: I' k* j5 q# Q实际的名称与之相对应.
1 I, b5 Z% a$ ]" G2 q; {& R( v由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
; \" v+ N5 F! p# z3 ^: K然而,更多地自由,同样也加大操作上的难度.% i- V8 k6 ~) J. J7 Z
以下就对可变参数的几个方面作一定的介绍.
( s6 D* E) |( o
& O2 u1 }" ]$ }9 W& u( k- P1)可变参数的存储形式.! ^$ {' X7 b7 E
3 M. A! J: x4 n7 Q: B
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
1 U5 F0 t# C* {2 ?9 o存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
0 I! o/ a& l4 U) G6 r" }在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,1 _7 |& d6 m1 O8 Q3 u1 V
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.( t+ O0 F- V' N2 }
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):) _4 l; n0 ~* {1 F" |& S! C
栈区:5 k0 [8 x3 E, J! e7 n, y S
) e' x$ i- Q2 m* A- n1 l|栈顶 低地址
$ O) y( L- S6 L S8 {7 w6 ^" i
1 j( o# S! ^! [" c% `|第一个固定参数var1
6 q e. r: j: m- P) l/ K% o7 v M|可变参数前的第一个固定参数var2/ I- ^* [" l. \. |6 B
|可变参数的第一个参数+ T8 J! t( {: S. a2 U7 m5 |- T
|...6 y+ s! y; m: ]/ ^/ k
|可变参数的最后一个参数
3 G- x# V, D* O. {|函数的倒数第二个固定参数var3
& V! e! J2 q" Y# e|函数的最后一个固定参数var46 e* s3 W, G0 L, K
|...
( Y& p0 z- m- G, ?. g5 E2 E|函数的返回地址, O( @, \! E- a* e7 r: E6 v4 f
|...6 S/ k& y0 ?1 E1 r: v3 w
|栈底 高地址
$ `/ o, e: v2 L$ v* g8 o+ ]9 i
! H6 W0 [; h3 A8 r1 L/ v* V& I+ K2)使用可变参数所用到头文件和相关宏说明. a6 {! k9 u5 J9 U# z) K$ g# A" u
2 Y6 R$ G6 G' J5 r9 K4 A& m! b在此,以TC2.0编译器为参考对象来说明.' s$ F9 W$ l3 ?- h4 ]8 A/ J
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.9 [ q" h, y7 Q Z& F. i4 M7 N! I
此文件为:
; z! S! n* p/ L- I/* stdarg.h
4 C; c5 F5 D8 ~3 O: q6 d* }; s7 ?0 Z& k, \6 Y
Definitions for ACCESSing parameters in functions that accept- S+ X8 U7 u/ d9 o
a variable number of arguments.: `5 k8 }8 G/ Q
2 ~( I L5 U8 d9 N' [
Copyright (c) Borland International 1987,1988
& }" {6 Z' \3 `All Rights Reserved.
) |0 b I5 Z# ]$ `*/
5 A I* g& Y8 ~, A |9 _: }9 f#if __STDC__. w. [/ }( f9 z) H- w# W
#define _Cdecl
0 M0 I* K$ ]6 D3 ?. p#else/ y4 C: ]$ _4 i7 h4 Q% Q
#define _Cdecl cdecl
6 m% l G1 H3 X, _: P#endif+ p# R# k; Z$ C% V+ E5 ]$ C% @
2 Y3 O5 ^! h; @, i. J! I9 A#if !defined(__STDARG)
( @6 e, Y R8 H# W O7 b3 U#define __STDARG
( f) T% b( f3 u+ ]& P9 z- z' y8 [9 q, Y8 P
typedef void *va_list;
0 O( x- y3 a# l' d% k3 J, D S
1 ^1 z8 o7 A6 X, L, w2 \#define va_start(ap, parmN) (ap = ...)
; Q3 S: o* g1 Q: n/ L* [, F#define va_arg(ap, type) (*((type *)(ap))++)
3 Z9 B: ^' w6 S9 V u#define va_end(ap)1 V8 a6 l/ g* W/ p t* ]
#define _va_ptr (...)
8 G/ s, d: _5 {' f" ] Z* T#endif& Y8 H( G a! C
: i7 N: f6 R: P/ S% J$ r; k
以上为"STDARG.H"的内容.! o _" m: @; r- p9 b
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list; X- O) K: J, o) n( d8 a9 D; N) U
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
# |) @7 I5 h$ [: }% {. }$ uparmN为可变参数的前面一个固定参数.
, Z. C2 @, ^0 e8 ^6 W7 Ava_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
$ C% Y; x) N! b1 V8 pva_end(ap) 结束可变参数获取. q8 p7 Q: r$ `8 x4 r
$ d4 L( s. y. a$ J
3)可变参数的使用实例+ q8 g! I, R6 ?& {
. o* v& }( N/ \) E3 b) p2 u5 s
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.6 T& Q* n2 p/ g$ r% g4 K$ E
( Q. G/ h4 C* F; ~7 s P
#include<stdio.h># e' R$ c4 s: ~) Z9 T P( Q' W
#include<conio.h>* h5 z6 `5 T" [* ?+ G, J% l
#include<stdarg.h>
5 ~$ j5 K9 a. [* J" h6 I& P/ v) @void tVarArg(int num,...);/*num为可变参数的个数*/9 i( O& C2 S1 z
int main(void)$ k9 O! o) |7 j% K( ` E
{* U( c/ e8 |; z9 [9 s0 `, t* ^3 _
clrscr();
$ d: p% T9 o' n' q4 f/ Z. Z# \$ A* T4 @tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
7 m" D0 T4 x* D, q" {. h M+ atVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
" u. T/ t0 Z# Y5 k- T- m3 i. Cgetch();
8 W4 M6 @' b. x# O; C, ~ ureturn 0;0 A1 R/ i8 z5 _
}
4 F4 m# F9 Y/ e: ]4 ~void tVarArg(int num,...)5 l, s: l6 w" c+ ]
{
/ l5 x e* v# ^1 [% O+ }va_list argp; /*定义一个指向可变参数的变量*/
) S. n5 f; D# X; ]7 ^8 S; Fva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
3 w3 p3 R# A$ J8 U Kwhile(--num>=0)
# v6 C$ _+ _4 V+ K printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,3 t/ S" B l1 ~
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*// k/ x+ q' \6 c) F, T* e, @4 D
va_end(argp); /*结束可变参数获取*/
2 B" E0 T0 q7 _5 D4 Treturn ;1 I9 q# j h4 h' s0 K
}% V# Z$ X2 I2 M/ h- q
8 o M6 B. c: u1 _* X* A4)可变参数的使用需要注意的问题
3 w' ]1 Q9 K6 @# i, C' `6 Z' j+ t U* j8 E6 l7 X, v, Q: n8 ?
1.每个函数的可变参数至多有一个.
' b2 j8 Z5 L; r9 b7 n2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
7 U, J+ c' B& X3 w ^; A' p3.可变参数的个数不确定,完全由程序约定.2 y1 L# k; u- y# O* ]# g
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
5 w. @( Z9 k, a2 [$ [; x) N而printf()中不是实现了识别参数吗?那是因为函数 # \) g( ]% R" c5 m. p
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg " g5 l2 \( R3 g* b" J% {8 Z/ q
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 , u3 y& w. i: @! S% ?4 g) v: `
过在自己的程序里作判断来实现的.
" p- @* w4 V* U$ \" y. v5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|