Board logo

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

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

可变参数的英文表示为:variable argument.3 |, b( `3 B: E  X" |
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
: c0 f, z" z) q) g' a可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
3 x* H9 K6 c7 p8 F0 i. V" g定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有8 A; p* i# r/ L( C, Z) o4 t% V/ i
实际的名称与之相对应.- h- H3 v* e5 \3 U
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.8 ]( w6 E; l8 u0 W( g
然而,更多地自由,同样也加大操作上的难度.3 R0 z) S! r  W* M
以下就对可变参数的几个方面作一定的介绍.
- u! K1 m8 F4 `4 o3 b
2 [/ o+ F6 L8 s: i  J" a7 _1)可变参数的存储形式.+ T% i* @1 l# `2 N7 J

8 V$ F& h* H! q  l6 i大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,- g+ u2 |% q& y6 G7 j/ P
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
) A; N1 e+ j2 S3 N( s在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,' I0 K5 \5 T1 e' P5 z
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
1 Y6 V3 H7 m. T6 Q  e# E# D) Z因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
3 ~! Y, w" b. ^  r( d. C( d1 X栈区:  G& S) b+ h% R! O; F
+ H' o- G) @1 e* s" K: W1 N/ C/ [. r
|栈顶             低地址" o' B3 d: q# A. Z5 Q* S$ g: d

0 J( v% o  n+ N8 L4 w|第一个固定参数var1
  Z" m& ?1 t, O6 R8 g, {& r|可变参数前的第一个固定参数var2
  q2 f7 O' r" R7 F1 h|可变参数的第一个参数
8 Q% R& z. c; |4 }  ]% p8 L7 _- A  J8 P7 C|...; z5 S$ \. i. G1 f5 U: _
|可变参数的最后一个参数
2 f" S0 I8 g8 z! l|函数的倒数第二个固定参数var33 z: Q3 w% n9 z
|函数的最后一个固定参数var4( p! U, K0 i+ R
|...; W1 Z* E( E. M3 s9 h" S: _
|函数的返回地址
5 [% u( r' m7 [$ n  B6 _( q$ P+ j/ ^|...
1 E' ~" f2 j9 w9 \/ p|栈底    高地址( X9 q- D/ l- e& C
  [( L' I. N5 x2 u4 @* D' k$ v
2)使用可变参数所用到头文件和相关宏说明4 F8 J: G8 C3 T+ p3 ]
6 Z" O5 e; q! P5 }1 x' Y2 N. c
在此,以TC2.0编译器为参考对象来说明.
: E) d: z& A1 l可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
1 Y7 j. k: |( p, W+ |此文件为:
6 K" t( K6 A4 w- E9 k* \  a# {; R/* stdarg.h
! P. o8 A7 u4 J! Z- N; Y. ~& [8 R6 f8 z/ k% Q9 a. n- X
Definitions for ACCESSing parameters in functions that accept& |! O4 s. U: x  d: Z
a variable number of arguments.
% \/ g. v! P; a  t/ ~
/ M- Q% N/ Z2 cCopyright (c) Borland International 1987,19887 {. {8 W6 c+ ?" s
All Rights Reserved.
+ I' d8 V+ y* o% z' m& \1 K*/' l  ~: a' s2 Q! j1 Z
#if __STDC__
( ^) Q* V, e- S2 n; t3 [#define _Cdecl7 G, X( {5 a% v1 g6 F4 }6 Q
#else
% ]  M8 D0 V% A# P#define _Cdecl cdecl
  q$ n  F( f& Y#endif
) u+ Y8 q) D5 ]" |: a+ Z( i0 g: S/ q( ^) ]0 }5 G; K
#if !defined(__STDARG)8 m2 [; x8 R7 E
#define __STDARG
6 b9 X; c2 y7 ?) i3 n# p" y! ^3 E/ M, h  |7 T+ J- T/ u
typedef void *va_list;, P. `6 G2 }  P% F$ C
$ _5 m/ x; e3 ]9 b3 a# f# E
#define va_start(ap, parmN) (ap = ...)
( ~' C. b8 P1 F3 ?7 o#define va_arg(ap, type) (*((type *)(ap))++)
0 Q" |8 k/ |5 @- b#define va_end(ap)1 w) a  F( r' P" j4 Q6 z
#define _va_ptr   (...)4 H4 j5 I. d8 V# ~2 s
#endif
7 C/ H6 }; w+ r, V* E5 I- s  E0 M+ |2 x5 h+ H6 Y
以上为"STDARG.H"的内容.
4 k. ]7 _" f* d7 Q- Z- E该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
# X% q' j  s/ K! x' C2 C) mva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,) s1 i, V& @& r2 Q8 c
parmN为可变参数的前面一个固定参数.
5 C0 W2 h1 [# H2 s2 Z) z4 Z# ^va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
# S" m1 z# l1 d6 Ava_end(ap) 结束可变参数获取.
# _/ H, ^& ~& h/ f, L, R, _5 G  `/ P0 }% T1 p+ S! D
3)可变参数的使用实例3 ^3 F' y7 x8 q% J8 t- A  V( i. `

9 X* k; R: Y( b& v) M4 B! g实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
- `5 d" J; x$ q  Z
3 l' b" m" @" v( S2 a$ C1 U2 w# s#include<stdio.h>1 u5 ]' N0 @% o. g5 R
#include<conio.h>
% B4 X! C( n: R" h#include<stdarg.h>3 M* N) k- q# g1 ~; c/ R
void tVarArg(int num,...);/*num为可变参数的个数*/
* C; R. c" C; @% ~4 E. oint main(void)( n; n3 `. c) Y8 ^
{& z3 x- L3 d# E# r, o: s7 E) F
clrscr();! [+ q' P& O' Q" [3 Y1 g* B
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
5 v  t1 }* D7 A; ?/ c& i; rtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
& H; e6 z4 @8 ^) b" n* M8 j9 t9 fgetch();
, z* Z$ s: [, N0 |( `5 Hreturn 0;
0 J% I% \% Q9 F}
$ X7 ]: E  M% ?void tVarArg(int num,...)2 e9 ~2 t2 H: x7 e; B
{
* K. H- J3 E! h! O! y  l. ~, rva_list argp;  /*定义一个指向可变参数的变量*/
8 p* ?% G9 Y- l9 |8 s  bva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/6 e: n4 _1 Y# d. B( j
while(--num>=0)& j) ^6 ^8 j$ F1 |
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
  ^& c# a) w3 T( s$ R    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
' @2 E* R( M" I& O- y, p8 j0 Nva_end(argp);  /*结束可变参数获取*/& t% U2 |. M* o8 K7 {
return ;
8 v2 ~2 N4 V, p; f' V8 ~: F  v* f  H}" e' y2 ]) Y* F' V- o0 \" M

) ~6 Y: R; c! ~1 S" g4 c& S4)可变参数的使用需要注意的问题
$ o3 G# p3 n3 z: D: y; Q  `7 p. u. M4 k) u: W* R! m
1.每个函数的可变参数至多有一个.
5 V% O& u; u1 B6 x8 `2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.4 A6 u& {5 ~. h: Z2 h! A+ R, ~5 w4 ?
3.可变参数的个数不确定,完全由程序约定.; A# H2 \7 n% T5 q% _2 ]( \
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.. F$ m% m1 L! w" S
而printf()中不是实现了识别参数吗?那是因为函数
8 V+ D3 X7 D* w. m3 W5 cprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
+ U1 F0 X' V; ^) J8 E9 L的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
2 b5 e3 K6 i( j% C7 {过在自己的程序里作判断来实现的.
' V4 n5 S& Z! d! V# R5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




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