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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.9 c2 A3 n2 D# H2 B; N u
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.% E* N& F' d8 l$ T
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不# H! ^( x, `+ x Z2 A
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
5 _1 W" |1 {5 {" ?实际的名称与之相对应.! @! r( f4 ? @! F* Q
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
5 y/ F2 g+ E; B w3 x# l3 R然而,更多地自由,同样也加大操作上的难度.2 A8 p. O5 S0 k3 ^( m4 ^( R* k5 w
以下就对可变参数的几个方面作一定的介绍.- S, A5 \" ~- ]6 ] r) h
M5 R- R, I% S; h
1)可变参数的存储形式.
7 f9 c4 ?2 W C3 u3 j- {" v4 X7 H$ x( u% \+ \" v2 Z* g7 r" ]2 h. R
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,, p9 C6 c# e% O" `) u8 F3 ~0 {
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
$ g/ U+ N+ X* W3 H在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
* E# ]0 c% e2 ]% S$ [- @! ]这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
; O4 f8 f9 r" B$ ^, t% ~2 b v因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):5 K8 o o1 v; k9 F$ Z( c4 Z9 w8 F
栈区:
' n+ p( U. f1 @% P( A; B( `# d4 B/ `
|栈顶 低地址
2 n, ]: g9 u" \! O8 j. k ~& ]4 e* x+ U
|第一个固定参数var16 }3 U: ~) P+ u1 h0 @
|可变参数前的第一个固定参数var2; ~- x" D6 {5 ^% w; c+ q% {7 X, S
|可变参数的第一个参数# _ s& Z1 H b3 I* o F5 _
|...: G' a" _& w2 C# o* ? Q- O
|可变参数的最后一个参数
4 U( d5 B7 s% R! b|函数的倒数第二个固定参数var3- @( }# x- a O( Z/ {8 |: P% h
|函数的最后一个固定参数var4
& P1 `: T7 p0 W5 }# t0 C|..., `0 W/ v, t+ g: Z& E+ O
|函数的返回地址& I* V9 V. @% w
|...
% f3 T1 d! p. y/ K|栈底 高地址
& T3 C, H0 `0 w, D0 ~( j* Z8 S/ j. g% b* y4 N m
2)使用可变参数所用到头文件和相关宏说明, P o1 Z9 h. A! g2 K3 Q8 O
% ^ O. Z, Q0 y, N( `, o/ ]! W3 {2 i
在此,以TC2.0编译器为参考对象来说明.
6 C; @, k R+ h' N. P/ b7 \/ c可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
5 f; O* u& o% g7 z# E此文件为:
4 \" l3 l5 D& i) M2 [4 z/* stdarg.h
- G- a) _( @6 ^8 G* ^; ~& Z. A: }& H. u1 V
Definitions for ACCESSing parameters in functions that accept# {/ D ]+ b7 X: r0 X
a variable number of arguments.8 n# G6 ?$ x5 i8 N& Y# I% }5 n; Q
( z' I9 I( ?' l% _( D3 Y
Copyright (c) Borland International 1987,19880 d2 A. j" U( w
All Rights Reserved.; S1 V6 g% x# ^: W. E
*/
2 z5 N: P6 M2 x+ w1 C! f#if __STDC__
) x" @+ S* T" ]; |9 H Q' C#define _Cdecl. @4 Q5 s+ H7 Q: Y
#else, F4 ?7 @% E2 D) b
#define _Cdecl cdecl
\8 U- A' T2 J' j9 A#endif
X' i% i3 S: l0 l
4 m& o) z5 A' A r# S* U; R; b1 Y+ @/ h#if !defined(__STDARG)
d0 w9 g; N) c' C2 E#define __STDARG; a2 l P0 p8 D+ A3 h$ A
9 d* L$ [! L1 S% Ttypedef void *va_list;
) n; h3 ~5 z& T8 a- e# z, V, `9 y% h2 x) y" K
#define va_start(ap, parmN) (ap = ...)
) y' |$ Y* \6 m3 L- v; [8 j3 D8 o#define va_arg(ap, type) (*((type *)(ap))++), H: ^! c; ~! n: W7 v
#define va_end(ap)
$ g5 V4 P! ?8 S/ }* g#define _va_ptr (...)
2 D! s `# n' Y1 U8 t# i8 D#endif* S# [9 ?9 P L# `! H+ g
5 b: `% v" t" @1 Q. \
以上为"STDARG.H"的内容." f6 N" T! {0 y
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;9 m' S# r' h/ w9 d' a+ [# G2 m
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,: T' s3 y; y; Q7 R8 S6 r, O
parmN为可变参数的前面一个固定参数. N: {& \: x* `; Q/ F* L2 F2 u
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型." U& f' J( Q; L$ H; q; r
va_end(ap) 结束可变参数获取.( Y; z+ G9 {. B2 w( I
' z( ?( x4 X5 n4 O& z
3)可变参数的使用实例
% |7 m( B2 o& a4 `, [6 n" E+ t q% i8 _. X6 F% A% t
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
% x2 p. g! g+ c9 u) R/ }# ]: G" N0 j0 r" x& \; r0 x% P% W
#include<stdio.h>
" \* q8 a4 s( U$ a#include<conio.h>
! v. M% p! Q( y9 o2 B- k- B#include<stdarg.h>
6 W; S2 ^$ g& q6 D' J1 @3 rvoid tVarArg(int num,...);/*num为可变参数的个数*/* _% [# }3 V" A4 Z
int main(void). n2 S& x7 V# A( I. a
{, `6 B/ }/ L# |: p. o3 Z. w
clrscr();3 q( c3 n. `. S# h, c! d+ c# P) q6 k) k
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
* K& Z& a7 c# L8 ZtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
# }# i7 ]$ h! ~, a' mgetch();$ `! t& z1 v4 D3 p' I" e
return 0;
7 W- z; `' O. ^' k+ R4 _$ h! d5 J}
# Z; L2 v% D/ q# f% Jvoid tVarArg(int num,...)$ z6 d& ~. ?- w8 Y3 x7 L6 D
{
1 n1 q5 ~1 e/ f5 A: b Hva_list argp; /*定义一个指向可变参数的变量*/
' @/ [% e* A6 u: z0 t* Ova_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/ ^* f' v/ U: w- l4 j
while(--num>=0)
0 N- }7 c& D; Z2 q" W# R4 p* `# B printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
# n- V9 {, T/ a- y( X! {5 o( i1 m7 c( O- O 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/4 t* o6 n# o/ \9 v8 ~5 j
va_end(argp); /*结束可变参数获取*/
1 ?% R. {9 Y8 f$ X. jreturn ;1 S( d) D7 Z% U- p2 c) L7 K/ `+ o0 v
}
$ g5 ]& w; d3 s8 |: a1 ]) x( x! m% z$ g! O
4)可变参数的使用需要注意的问题
( ]$ q1 n. X5 [ C* h k, v
, Z: e! q7 O7 d* a0 n1.每个函数的可变参数至多有一个.
/ X, l6 B1 W g8 }4 u2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
0 {) X6 q, t2 m- S8 o: A3.可变参数的个数不确定,完全由程序约定.& c+ e8 ^- r# P* X, h3 R6 B9 H8 c
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
7 [% y$ s2 N( _4 K而printf()中不是实现了识别参数吗?那是因为函数
4 L5 }3 e0 U# }! `printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg ' R9 h8 W$ L; C8 B) S$ l% \/ k
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
. y* _# S) @) m( C1 q过在自己的程序里作判断来实现的. 9 Z0 I+ z3 Q' j3 o# c
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|