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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
% o! r& s, H& j% ?它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
/ ]( `! q, w; T5 c, P可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
$ \: V! N. u0 |% Y: A! J0 g定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
$ | e" q& Q! `4 x; s实际的名称与之相对应.) ^! M2 `9 `; U
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.5 B; T3 m7 r; X) Y% z! k/ H6 N& L
然而,更多地自由,同样也加大操作上的难度.) i* ?+ X. A; K+ z. g6 J
以下就对可变参数的几个方面作一定的介绍.( F! G7 B6 w5 ^* e4 w
1 F( L$ n+ I: I- ^9 V5 \ s
1)可变参数的存储形式.
, z9 c9 q7 ?# L a& \1 K# i8 W. g; o
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
( {2 _2 E9 B" I3 H$ f1 b存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.* }* G$ l3 Q6 ~ G( r. @' K
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
# v% o# ~# \1 ?+ K4 C, F! P这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.' F. d" P4 F- D+ T
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):& ?! ]# W$ K- U! `& A9 e, h
栈区:
2 @- ~2 J& R: F+ T" ^* s, U& G* t
7 z. @5 R; w" {1 y$ D, Y|栈顶 低地址
" O4 o" r Q" s3 c: B
# Z8 Y* E# p( P5 `|第一个固定参数var1
$ Y* C0 n' ? C& `. j5 K6 W|可变参数前的第一个固定参数var2
: c4 U" @0 B7 i|可变参数的第一个参数0 f+ F! ^) O8 o/ I' {' h, l0 l0 \
|...! Z. @. d1 P2 L. P, ^) W5 o
|可变参数的最后一个参数
9 S4 Y& Y' ~( ]; A- M|函数的倒数第二个固定参数var36 K. i% M/ E8 q% B+ M2 L8 T
|函数的最后一个固定参数var4, ]* j9 x$ ?) v: o% @8 }* ~
|...
" w( M3 k+ s; q: F9 i4 h6 S2 k! M|函数的返回地址
- M9 ?, R( o7 k3 E* [- C|.... J$ `% _6 J! L/ I% }
|栈底 高地址
, p$ x D6 P! |4 L' E2 Z9 e9 G& J# X( g+ [/ G! Y4 T' i) F: z
2)使用可变参数所用到头文件和相关宏说明
" p( e; S5 T3 V9 ~* B5 | Q' C! V2 x4 D; w7 |! B' z0 ?
在此,以TC2.0编译器为参考对象来说明.; |" a8 A( w. J% G! T
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
2 |2 `* ^; w. P% G$ V& C, y$ h此文件为:
( U; H$ |. K) F% q3 U/* stdarg.h$ x& N9 n! p2 f3 z; f
5 [" q8 y2 q5 q ^# E
Definitions for ACCESSing parameters in functions that accept
1 `/ x A+ X( Fa variable number of arguments.1 r% j6 P; r+ D: O1 r" L# ?
6 v& w: @8 F" J
Copyright (c) Borland International 1987,1988
: |2 q7 c- g$ Q3 ]All Rights Reserved.
( n2 Y7 p7 A: m" {*/
8 {! N6 b0 v* [ Q+ e#if __STDC__3 i9 N7 m2 Y8 L( M
#define _Cdecl, e& b2 U; m7 V, a0 a% U3 ^
#else$ I5 Z& s+ x0 C
#define _Cdecl cdecl
& d' Z! m, _3 k! L#endif
* p/ x0 j0 j2 B) ?* y; t0 G: k6 K, W" [& c- [# M; @
#if !defined(__STDARG)! ^% J+ Q1 E; l
#define __STDARG+ ]) T- q: u" |/ ]+ e( z
" V% A' f# L2 u- W+ X) wtypedef void *va_list;
5 d( q I. T1 S- Q/ f5 S6 y+ Y" w% x+ Z
#define va_start(ap, parmN) (ap = ...) L8 q* r4 e! s" K W: `/ @' V
#define va_arg(ap, type) (*((type *)(ap))++)
* p0 W2 ~2 K, C! t( V#define va_end(ap)$ `- Y" Z) C$ m- q
#define _va_ptr (...)
+ ]) F5 I4 b0 i9 U#endif
5 ?5 r0 l! d& K+ g( x
4 i3 K' B& S& _7 n% V以上为"STDARG.H"的内容.
3 V0 V e, K. H$ }' t3 r7 E: U7 `该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
" l1 K* `) p) Z& `va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,1 q& y# E4 [* x0 Z
parmN为可变参数的前面一个固定参数.
" k* i& R" ~" G6 U0 s. d, `va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型./ z. z( O. ]5 F; @6 f* H5 }) b
va_end(ap) 结束可变参数获取.& |$ q# x% m# o: M& \
+ K0 i) v; Y* y* A5 W
3)可变参数的使用实例
0 s( i, M# A* Y+ h: R* j+ h
- h) `, r9 c* [5 W8 V% L8 y& W实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串. ]4 ^" Q/ a& t, ~# t# W7 \5 z
' ] I3 ~+ |5 t( G#include<stdio.h>/ }3 k! e9 y% }3 T& i# s/ A
#include<conio.h>, D$ @( y9 d+ z/ S. r& h H I5 b
#include<stdarg.h>
, Q, ^: g* e/ u9 Q( lvoid tVarArg(int num,...);/*num为可变参数的个数*/
/ u# A( W% b } E" P: yint main(void) v C4 `/ \" F' [
{/ [, d% K: G& w7 o) ^: v2 u
clrscr();0 U" B( K9 ^# V2 C- i# \' {
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
8 q6 N0 L: i0 y0 u6 P* M% X" ktVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
- ?/ p* l1 ]6 I) `3 U" Igetch();7 C+ [7 Z, e0 d
return 0;
8 `1 T6 H0 [2 u+ C7 L( \}6 l; I0 k. S( F4 d' Y q
void tVarArg(int num,...)" o: N8 i' [* R1 |; ]1 h- q. P
{* ]' q" q; V' U$ y8 _
va_list argp; /*定义一个指向可变参数的变量*/5 @! G# X8 M' v/ p# t: `% b
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/. S7 r5 M( l* F$ O+ k
while(--num>=0)7 M8 h9 A5 m8 M$ d* q, E8 K7 o: H0 M
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
$ ]& K( i* ] Q( V0 X9 \2 @! O1 O 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/' i" K: v* D1 O4 T) d# f
va_end(argp); /*结束可变参数获取*/7 ~9 e( y1 s1 H9 S3 k3 w, r$ [* c
return ;
& o, {$ X3 l, Y' k0 J}9 q% z: l$ g6 v/ ~' `% P1 z; O! y
+ h, J$ a0 C. Z3 ]/ y4)可变参数的使用需要注意的问题4 G0 l& u/ g1 t& Z! K6 |
' D1 j, o7 w1 l
1.每个函数的可变参数至多有一个." L9 [% |5 i- l, ^0 i
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数., p2 P% q$ p& ^
3.可变参数的个数不确定,完全由程序约定.
! O- e; n( i) X! Q* N4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.# C. V+ m& S' e. G% o
而printf()中不是实现了识别参数吗?那是因为函数
% l2 S, `1 i% S; n1 Sprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg A) b3 h: A& f/ {* t6 Z0 i) Q. w
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
0 d$ u3 s5 v! N" E+ f过在自己的程序里作判断来实现的. 2 S, w1 { e# I' U8 t
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|