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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument./ f0 @1 ~0 h2 B- E4 L3 `6 x
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.3 ` r1 U* Y1 U* o1 G) |
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不7 y: I2 P+ n: L& G* h% e
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有 ~) ]1 m9 ~" m9 M" |% }
实际的名称与之相对应.4 o2 g4 x s0 q: y- Q2 I
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
4 s- S$ h/ ?6 D3 F2 M5 Y然而,更多地自由,同样也加大操作上的难度./ `: C h' Y: m* @0 g& q
以下就对可变参数的几个方面作一定的介绍.
' |0 y4 J2 K# R2 x2 }5 {9 `. K+ m$ w, v! e
1)可变参数的存储形式.
0 S3 e' r' t. z* _0 d7 Q, A
: i m, J6 n: x `/ f- Q) k" @大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
+ e5 o9 A$ x) L s9 l存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.7 i2 p6 [; k! T* E1 E/ t" ?
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
& |) ~7 d ~# P) }这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.7 _" H8 d" l* o" L
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
6 |. E4 J" k: k2 [% E栈区:9 b! S2 Z' o0 M& Q: m
, L# P' {( b4 p7 u
|栈顶 低地址
" l0 T) [2 G; P3 I1 [6 b$ U: i. w9 }$ C4 X& l% ?; X) y
|第一个固定参数var11 P: F6 \6 n! j! ]1 F% K
|可变参数前的第一个固定参数var2
; h# k* ]5 R* S|可变参数的第一个参数
0 f" [2 p8 z+ y, @* B- V% y3 c|...3 g ?' m1 e6 M9 U7 U5 N+ V
|可变参数的最后一个参数9 v) H% s/ R% o2 J% V
|函数的倒数第二个固定参数var3* x. M# f* u8 A4 f. m( F6 i2 T
|函数的最后一个固定参数var4
5 |9 |& p! F# j' P9 |3 Y1 W- }|...
3 v& k0 K/ `, a& g|函数的返回地址, x/ K3 o5 ~- v( a2 ]' h
|...
& O) d( S# T2 g$ J6 w|栈底 高地址& P1 Q0 Y$ n0 ~8 g" H
, w2 [, @( @ y9 \2)使用可变参数所用到头文件和相关宏说明
) m) b/ X0 f& N, A
6 ~* m9 S! {/ `* p; o在此,以TC2.0编译器为参考对象来说明.
?3 C( s( A! R3 L+ _( X; h可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
4 l6 z; D- g# P) J( K) |此文件为:4 }4 w& c9 ^6 _# V( V! u
/* stdarg.h, ~, [# M6 L, x4 k( i
* e8 l* u" i0 eDefinitions for ACCESSing parameters in functions that accept4 o$ g1 s( u) p% t* z+ C
a variable number of arguments.
7 C5 O- _! V+ t$ ]2 I5 m7 N% ?- \+ D: B4 ]/ t' \2 |" M* b, d/ {
Copyright (c) Borland International 1987,1988" q y0 f) I( A
All Rights Reserved.
% q: a4 ^1 P9 ]( N*/
2 L, ?& p0 t0 y6 K8 p3 ] i#if __STDC__( k: T. ~7 M; y& p7 a, N% q6 V
#define _Cdecl
3 ~2 \5 Y) y: I3 V, i( {#else
Z3 v& S( \! w' \/ s2 P#define _Cdecl cdecl
3 r+ b2 B/ D! E#endif! Q9 s! l6 R' N }+ U
( c2 `8 k4 D) m4 o: a#if !defined(__STDARG)/ k3 \* @* Y0 x9 s. V* [" g5 L ]
#define __STDARG2 Q( o7 m1 h3 C0 U& x
' T# I+ c. l3 D$ ]# V
typedef void *va_list;5 B5 C* w6 Q5 \
8 ^/ R# C( b& R" Q% K7 |, X) B3 X: L
#define va_start(ap, parmN) (ap = ...)* Y6 e4 b" b, s {
#define va_arg(ap, type) (*((type *)(ap))++) X- F$ M, c" u# \8 A
#define va_end(ap)" x5 K( w+ r% p0 d- @( O
#define _va_ptr (...)
* K& }7 c5 ?& P: f6 V1 r#endif. h' b- P2 ` I0 Z1 m
5 P& B. Y2 \/ Q8 R以上为"STDARG.H"的内容.
& p7 u, \# ]5 \: X6 j/ j) c( N- f该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
& m1 F8 w% y2 ]/ ]& z+ L2 Zva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
R0 S% F1 t/ X4 }) P' S/ b+ u: T0 EparmN为可变参数的前面一个固定参数.
% ?8 Q2 q, i9 ~) y: g3 `3 L$ Xva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.3 q0 a: F& F2 `
va_end(ap) 结束可变参数获取.9 e9 v8 g+ D k0 E5 W, ~
9 s6 S0 X$ _% u& R1 A* S
3)可变参数的使用实例! u3 _0 R; g0 F8 X0 b4 u5 x) q
Y" ?$ U: {/ O8 V) T' L$ U
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.9 _8 \& P1 T8 P& d* n
9 v9 @; d. H" l1 Q' N6 Q* \
#include<stdio.h>
2 r7 W* \; Z7 G$ j. x4 v+ e: Y" D#include<conio.h>
: q; ^7 i* R- Q0 a: t$ i#include<stdarg.h>
4 |. t0 t* A- a% Gvoid tVarArg(int num,...);/*num为可变参数的个数*/
# ~1 Q6 F2 ]) _" Pint main(void)- A$ j7 Z# M% Y% S. h/ [* S! g2 y+ @
{
" p% r0 @4 }& f4 B! z, zclrscr();7 U8 n2 J' o- H* C" L
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
& S: U: X0 h6 E& L# S& E/ D( btVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");9 x& y6 D( Q9 U' J9 d- E1 L2 z3 {; G$ y
getch();
3 K+ t% J. c2 e0 e, Jreturn 0;
9 D0 z: V& a/ u8 V8 B% [ A( ]4 p}6 t/ u8 z7 E/ l6 [8 v
void tVarArg(int num,...). E" G$ @2 ?9 n
{( g4 h2 A) Q+ M: f9 L3 X* O
va_list argp; /*定义一个指向可变参数的变量*/
/ w+ Z" i" a3 L+ m5 j6 ]1 @va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
@" D; W! I: {( g V0 H3 jwhile(--num>=0)
( d8 p( s/ m2 e- \+ l9 j printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,0 c2 c, v' N, C8 M! v$ Y9 S; N$ _7 v
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
9 M$ q3 B% Z$ h4 i2 u8 u7 X% Eva_end(argp); /*结束可变参数获取*/
' V" s, v0 q1 V4 jreturn ;
* Z+ A8 ]# P# z. `}
9 l J r9 Q. K* B+ x1 `
9 L5 w! R0 G4 I; Z8 b5 N2 Y# O4)可变参数的使用需要注意的问题9 g& O. j( P2 _0 I
, U/ Y3 `4 L! k) Y5 Y j. R2 q
1.每个函数的可变参数至多有一个.. K+ h4 g& y4 K8 @$ G1 k
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
; W) g9 @4 Z4 V# g6 r% C3.可变参数的个数不确定,完全由程序约定.
2 h7 Q3 n& W5 y# }# w& [4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
" U# Z q# \. K& S p. G而printf()中不是实现了识别参数吗?那是因为函数 2 N8 W0 g) z8 @8 l2 l7 P7 M& \' q
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
. I2 p, s' ~% Z% }4 I的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
) N& s9 d2 k. x+ m: X过在自己的程序里作判断来实现的.
, s" N. }- y& p/ m3 W5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|