Board logo

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

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

可变参数的英文表示为:variable argument.
1 B. H) J3 J7 Q/ I: q( }, D* A6 }它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
7 D3 k7 j6 O( ^, @/ x& i, O/ P可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
  V$ Y6 J& Y2 @/ `) u! a定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
. g0 `8 _0 k3 Y. P& K. B( y( e! i6 u实际的名称与之相对应.
! }" f# y  c% B由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
/ d7 c9 d* f6 H然而,更多地自由,同样也加大操作上的难度.
: B. }4 C: b2 v" r7 g: @以下就对可变参数的几个方面作一定的介绍.& b5 a5 {  |$ m0 B8 e6 t

; I* ^9 z, K5 ~0 b7 m0 U( R! {1)可变参数的存储形式.
# p+ [5 z5 {+ i+ V; E
) J! l( h/ u+ F& m6 s) I/ G大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
! y) S2 N7 n! y( {% m% `) r9 E存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
% u7 C% P" O( B4 }; S' |2 g在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
( k) X# C% S! i! _这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
3 T: K4 K8 {0 _1 P0 ^因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):1 w& ]7 m0 o% f& v
栈区:
% A& u& v; q) w4 n1 c) d6 [# f) C1 `3 N: E' t/ p' B& k9 E/ @" E9 S
|栈顶             低地址$ T; C! L0 z0 B2 Q
1 b3 a5 [/ M3 _+ P
|第一个固定参数var1
9 q# a  C& g# r( w2 \) J/ {. d|可变参数前的第一个固定参数var2
5 S6 }. K8 L9 w7 V# e6 \) L|可变参数的第一个参数* S- w+ O, ^! k: H( }% l
|...5 u1 n3 c. h( F5 H- j
|可变参数的最后一个参数
5 q1 s0 g9 j) a% ]) f+ H|函数的倒数第二个固定参数var3
& B; j" c1 A, v  b* j|函数的最后一个固定参数var4
9 \5 D( n7 U  ^& I( p0 H( f|...
1 x# P- Y) U. r: I7 g/ f3 ]& U$ J|函数的返回地址
0 l/ ~2 `0 K5 W! u% f7 @7 f& |3 Z8 o|...
8 c- a" |& J1 E6 f# @: t8 Z5 w|栈底    高地址3 G% e  i( J- }. g8 j

1 n! t; O* g, s8 k) B) n/ d2)使用可变参数所用到头文件和相关宏说明  I, T; F1 O1 G) o1 P" G: L
/ ^. O& q; g9 K, G" K( S- z7 p
在此,以TC2.0编译器为参考对象来说明.
" t# I' h" @5 k! I* ^# `可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
& h& E* L4 \& @4 [  U6 S此文件为:
# H! W4 d& K5 O$ v/* stdarg.h7 u' r3 I5 U; a) Z) _

# f* Q/ B6 b: \, p1 G/ L$ FDefinitions for ACCESSing parameters in functions that accept* M. k6 G4 Q/ a5 f4 w! M
a variable number of arguments.( [% W# v9 k( D7 x/ ~6 k

. d  @* s& c& n2 L2 A* k$ v6 dCopyright (c) Borland International 1987,1988
' r4 k& C3 B7 h& [6 |$ TAll Rights Reserved.
0 \- Y/ B9 {% c* d* f  L*/
0 ^2 |% P7 C1 h2 J3 t# `#if __STDC__  I: n& e2 h; |) C9 ]. G
#define _Cdecl! Y7 u& o8 K/ ^4 K* d$ b9 E- }
#else
7 P( W6 k" g1 a$ M4 C, P#define _Cdecl cdecl6 n  i& F3 c9 `% C/ G
#endif8 S3 H; a. F4 \( Z* h" O/ r( _, K
$ P/ H" i, Z5 t. Q; m, j
#if !defined(__STDARG)6 |. u  q+ v. n; m; E
#define __STDARG. H8 @' Q( B& e( H6 k9 w5 Y

8 x# z5 K0 w$ A4 f& h- x. Otypedef void *va_list;
4 N7 o6 j5 r. n/ M& J+ t2 R5 i3 p
#define va_start(ap, parmN) (ap = ...)" [& M* H- f+ F" q3 ~8 }6 j8 c
#define va_arg(ap, type) (*((type *)(ap))++)
; I& Y& E2 d# G+ `# d#define va_end(ap)5 P: N! A) U3 o" g6 k
#define _va_ptr   (...)
  A6 _) H8 r/ X% k5 y* s' u#endif
$ @* ~8 U; k  {2 z( A1 q; J& Q1 M6 {+ e' ?7 r
以上为"STDARG.H"的内容.
4 g; H. n5 Q9 {7 u6 o$ @该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
8 p5 d" f% p4 w% Cva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,. S0 K  _2 I& B) X7 ?" ?
parmN为可变参数的前面一个固定参数.
1 K, }, |- o4 l$ f# ova_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
8 E) }: g& V! Zva_end(ap) 结束可变参数获取.0 {* S+ h' Z1 D. I8 }5 m/ J" j1 X

$ L3 a( u% u+ r' ?1 N4 T3)可变参数的使用实例) Z/ D( `/ q( \4 p/ U2 _; j
0 v& @3 ]8 u2 m# q
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.! X5 v' w; x3 h$ {8 T! _2 H
7 b6 A" X: N0 ^+ h
#include<stdio.h>  Y4 s9 n8 Y5 `
#include<conio.h>+ l" R! `( I( y9 D% s
#include<stdarg.h>/ `0 X* b* b" r% g% j; [" r
void tVarArg(int num,...);/*num为可变参数的个数*/. N8 B9 p/ C( l* W' l
int main(void)
, r" C, o8 [) i4 a{1 z" Q* P) b% F; s( ?8 I
clrscr();8 X; ^0 q* D9 `! |9 a% e
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");* T, G( E# N, K, b( T# M5 c
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
! @4 Q+ I. x" y- xgetch();" N8 P3 V. \$ Q: \
return 0;, e1 H3 n2 e0 w3 e3 o
}
1 v4 y4 K6 K5 ]# [( o$ [void tVarArg(int num,...)$ p, A6 h9 \6 m+ p* U% O4 l/ \1 @
{
9 _* Q& e+ O0 i1 H, d' k+ Vva_list argp;  /*定义一个指向可变参数的变量*/
$ E6 J7 I7 t& I7 D. Iva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/( L$ V. B$ \* u! b
while(--num>=0)/ Y0 _; J/ ]' _+ ^* ^
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
) s. t  x5 ?& x1 o" a$ Y9 T    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
+ ^! W& T, N8 j7 v1 Ova_end(argp);  /*结束可变参数获取*/8 p( S* M+ N8 K5 G
return ;
9 U9 M% D. {% P% p}) [% x# i( y3 L0 u
9 a" n' Y1 O9 k0 e, C% o
4)可变参数的使用需要注意的问题
. C7 }# r3 Q; U: v8 S5 X5 _& }/ h/ d. L3 w3 u8 L' K, h  Z4 E
1.每个函数的可变参数至多有一个.
6 e8 |0 U+ S3 [+ y6 \' Z2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.( d: t+ H$ U% l/ x- P
3.可变参数的个数不确定,完全由程序约定.
5 ?) }: K. }- j* S' ~' g7 I; B, l( a5 Q5 \4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
! ?9 o# X1 {' n- Y而printf()中不是实现了识别参数吗?那是因为函数
* v7 S" I: {5 ^  Yprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
0 @6 `+ ~4 M5 l. O的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ! N( N& l  o" o2 ?% \
过在自己的程序里作判断来实现的.
- A1 `- F% X1 R- R. x# D2 l5 C5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




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