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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument., r- r, j5 f- X! S, \; p9 s; L
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
& ~2 q$ f' s" N可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不! I6 S* y2 a* ~* m3 E3 B
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
. Y- U$ E: u% i5 F/ Y实际的名称与之相对应.
( W1 _2 M& ?# f8 n* e由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.; @, P( `/ K- `. N
然而,更多地自由,同样也加大操作上的难度.
7 g9 p# [) W" J/ A2 b以下就对可变参数的几个方面作一定的介绍.
/ C- o. R6 w' N& I4 x9 l' a) F, _+ K" q4 C
1)可变参数的存储形式. [4 G: K# z4 M1 b' i1 e
) _* R8 C6 w* t9 S$ F- g
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
& R6 @# O! Z. {' u' q存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.! d+ x5 R# [$ k
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
6 A4 C. e$ N% s这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.4 K$ @3 y- P) P8 i8 L& N( N
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):& s; Z* m4 _& w. G3 b
栈区:
. Y5 R( u2 h4 b4 }, J# p+ x# i2 O
3 w. H, M+ ?: x" p|栈顶 低地址# Y9 f, W% P/ d, a
- d/ H; G$ S5 s8 T|第一个固定参数var16 x" V- Z A$ n" w- ]
|可变参数前的第一个固定参数var2
) K/ J8 q: K( }|可变参数的第一个参数
8 m/ ?- {: g' E& A! y|...
! X2 g; ?4 c: c, M0 v|可变参数的最后一个参数
! q$ p% w. J3 p|函数的倒数第二个固定参数var3- I. N, B! P5 |& F- n
|函数的最后一个固定参数var4
: t4 |' f9 i; I/ P0 M|...
7 R" k5 i; W6 o|函数的返回地址
/ S& c& s+ j) H. a( v3 S0 s' T. [|...
/ |) _- _; y+ _6 O/ x. R|栈底 高地址
1 X4 }# a& o% P* G4 |) I* o, P+ d6 h. ~
2)使用可变参数所用到头文件和相关宏说明6 Y, U1 p2 T$ @2 p! c
$ e @0 y7 ~- t( q: Q0 o- m
在此,以TC2.0编译器为参考对象来说明.+ h2 y- p! _9 X- K- `
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
! A7 K9 L2 [% T( _3 Y, [: T此文件为:
% W7 @# [9 j$ y' x9 V$ d/* stdarg.h- l" h& M/ I& X; x, t" A
) _% o' w' F: w4 r$ [
Definitions for ACCESSing parameters in functions that accept
3 c5 t1 a- f: I, p& s- ca variable number of arguments.
; h& X. _, z2 K; b+ K4 [% L- B' R" V5 N; G. x
Copyright (c) Borland International 1987,1988' [" K8 T9 A7 i8 z/ E8 R
All Rights Reserved.2 y( |1 @4 i! n
*/
& Q! a( T; a3 N% G7 E: f# K( m! Q#if __STDC__' b; x2 c. \1 _2 K$ r
#define _Cdecl' {9 w. p& c0 s2 |6 M6 l' ?, P
#else
" L0 [1 z% S9 p8 I+ w3 @#define _Cdecl cdecl1 p' f# r6 w' L4 h" \6 g
#endif
& i' p: u8 ]. `3 v/ D5 c% S/ Y: j, P# `; ~5 D/ D$ m( G
#if !defined(__STDARG)
3 D- d. C- x ~, C( s#define __STDARG7 U! M/ U& {) ]5 C8 E
* ?7 P. Z0 x- ?! B7 U/ c- D% _typedef void *va_list;
# |& K) [8 R) E1 A8 q, D, w. ~( I3 w! L/ t, R8 I
#define va_start(ap, parmN) (ap = ...)' y+ ~1 ?, ?. t3 M
#define va_arg(ap, type) (*((type *)(ap))++)
- L0 [4 ^& J# s#define va_end(ap)) p9 b8 x& B% j# X- u4 ~5 B7 c- p. B6 r
#define _va_ptr (...)
: _, Y' B; g/ t$ A% C#endif; C/ X0 ]0 k- ?/ C
$ J7 N! @4 L- ~6 S7 M) T
以上为"STDARG.H"的内容., r: O3 f$ q+ b
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;7 `3 p1 U& }1 ^1 D
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
8 x( l% c7 X% R1 Y3 t4 FparmN为可变参数的前面一个固定参数.
0 [* t/ Y6 E' M' Kva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
5 i9 e2 G# G) s; r9 yva_end(ap) 结束可变参数获取.
( g/ h# X# D+ u' t. H4 O# n+ h9 f( z* G2 z4 |' F# r1 ~! k
3)可变参数的使用实例
1 ?8 g0 [% Y0 X5 B2 z4 G' {; n& c: |# z+ ]0 A k! G
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
. b+ L, @. n7 t4 c& e% C1 U" o1 P& `) ?; C3 H
#include<stdio.h>6 N+ @; p U, I! t4 I: I, ^
#include<conio.h>: H% V! h" _7 v6 L
#include<stdarg.h>& N* y6 T8 Y; R( {2 T( s3 a4 q3 D
void tVarArg(int num,...);/*num为可变参数的个数*/
/ L9 _6 ]( }. u: j/ V0 |6 Zint main(void)
" x5 B1 ^6 ~3 l{1 f' @1 }: z+ _! a' K) ?7 g# v2 G
clrscr();+ r1 ?9 ^3 U, c3 i( s* o) n
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");/ s; M5 o! ]- [3 l$ S& H
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");0 j3 N! n* I$ n1 n! S. s- _
getch();
: \7 V* o/ ^# areturn 0;
0 `1 Q9 p/ H3 y9 Z, [}
8 [. }2 C3 n* ~' G2 J9 I% V- P/ Fvoid tVarArg(int num,...)( V; p; _& J' C- |# G! y0 ]
{
2 `# o+ o$ o/ [- p; F! Yva_list argp; /*定义一个指向可变参数的变量*/
/ z. ^7 o9 b0 _! R0 ^. O6 rva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/. P& B6 d* n; K6 E* F! _
while(--num>=0)
9 `4 ~9 O: |+ `3 ~( v8 |" _/ m printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
8 a9 W* F4 I( D 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/4 K4 Y5 c' v4 K; a
va_end(argp); /*结束可变参数获取*/
/ A. Z9 ^5 m* v% h3 U- A/ w, I) Q: preturn ;2 E* q4 w# w* v7 X# T( G- G0 Z
}
) O( ^8 _$ f- O/ H" z5 W7 q7 x$ h; M5 { v1 j% Z
4)可变参数的使用需要注意的问题, f" B8 ] r2 I o& H( L, d8 x
7 i! G1 O9 z0 H2 @4 C
1.每个函数的可变参数至多有一个.8 \% a3 b) d7 r/ D5 h! h
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数." d8 [) C. g6 q5 c9 C# {2 ?: P+ x
3.可变参数的个数不确定,完全由程序约定.* I, L/ |, P7 K' E9 c6 x) O
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.4 q$ C+ g8 d9 i+ ^8 \$ ]: H
而printf()中不是实现了识别参数吗?那是因为函数
- v- r4 g/ A. d7 K# _1 Wprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 0 Z) m+ U, V: S, p+ A! ~! T5 u
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ( w" M, C) }3 u* c3 a
过在自己的程序里作判断来实现的.
( m: Z. F# i% z7 w9 v5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|