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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
1 o& f( w! I* G8 I" ^; p9 j) a它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.! {9 M1 B& \# Q$ V6 j" h9 A
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不9 w8 t7 T4 Y3 k8 O
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
2 M: Z# Z2 C$ C9 d- o% r实际的名称与之相对应.2 J7 s) K7 j) c1 y! y2 o
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.* _4 R9 q/ s# l( N& g
然而,更多地自由,同样也加大操作上的难度.
) Z7 ]" d3 ?) E& P5 a- D; ~以下就对可变参数的几个方面作一定的介绍.
- `" F) Q4 H7 t W; K2 ~7 P
3 f F, K) O* m6 r' `% t9 W. N1)可变参数的存储形式.
" ^( b- D0 P: f3 d l
5 u5 w' I+ } a- \4 D; H& \; O大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
& p4 q0 x0 d# l7 e ]存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
' _' O1 m! o+ g# P5 U7 G5 ?在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
1 [ j N$ v+ |& O- J1 z3 v这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存. h g7 |/ n. K: R4 ~# R
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):$ I. ]+ d: Z* K
栈区:( ?/ F" Z; E1 F4 H; X; w4 Q5 b/ b/ g
4 H2 T0 A( C2 d- X. t! c1 a|栈顶 低地址
6 T" q% @' h- b; P/ s& s
, U3 }; ^7 U! A# }- J|第一个固定参数var10 Z0 q: ?: I7 X. M
|可变参数前的第一个固定参数var2( Y2 j& W6 W, i0 F* }
|可变参数的第一个参数# ~7 w: o Z' \2 Q. M
|...
' F C' i$ l4 _4 G|可变参数的最后一个参数
7 b; i7 y' U3 e9 Q* h v7 l|函数的倒数第二个固定参数var3
3 C: W! U" R: |/ U# F|函数的最后一个固定参数var41 e) S- k; K0 o& }5 Q: c( N3 G4 [% [
|...
( H+ \& X- _) K" h9 `: X4 U|函数的返回地址! P1 l+ x7 H& N# D
|...
3 Z" Y( h, L3 M7 T; _|栈底 高地址7 u: {$ I* P; A6 o" M
) S n% V0 o1 a) B( U. e( r% r6 K3 |2)使用可变参数所用到头文件和相关宏说明
% M: B, e( s$ L! n4 G( {8 E' G+ S# ?9 D8 k
在此,以TC2.0编译器为参考对象来说明.
# E: z+ ^$ ~$ Z& O2 b2 G可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.+ K: a6 A. H# D; M
此文件为:) ^4 @" s2 _. U% l2 T |$ g6 A
/* stdarg.h
- Z. m* w+ B- R1 ?( h% W% ]
+ N/ q, x: v+ W. |! iDefinitions for ACCESSing parameters in functions that accept7 [6 V+ c+ ]( G" X+ G7 A Q
a variable number of arguments.
! _# O5 O8 m/ N& n# e8 a+ I1 S7 W4 G; `
Copyright (c) Borland International 1987,1988) k. M' J4 e3 u4 N1 [/ N& k9 r
All Rights Reserved.
, o. }5 {5 f1 L9 ]% z, l$ o8 s* b2 p*/9 m; N& z9 O! g% A
#if __STDC__
/ a4 \, p9 Y+ p/ ` N2 D" r& R#define _Cdecl
. l3 S7 k4 X9 f8 ~#else& I5 j' o, e( W5 k
#define _Cdecl cdecl! N- G) } C; d% J7 ] z
#endif' q o. t- R. F; c+ g3 `
' m$ c+ K. e l# F% J! t
#if !defined(__STDARG)" Q: m# R8 s. \; r. j% {2 K
#define __STDARG' U3 |( K* S% G9 _" r; C9 N* {
: F8 r& s) v e% M3 V
typedef void *va_list;
( C; D4 n- D/ H0 k7 x) O, P0 ~( W+ _- {
#define va_start(ap, parmN) (ap = ...)" p* l8 I+ U8 n( L
#define va_arg(ap, type) (*((type *)(ap))++)
) Y4 R" u$ W4 D% }3 @% y#define va_end(ap) S- A' ~" y& c* R( t
#define _va_ptr (...)' X- A+ k( P1 N9 n* F) R
#endif
' u: g$ O0 O9 g/ M4 M5 _" R6 L6 K. j
以上为"STDARG.H"的内容.. }8 f2 V$ ^ V# d; k& y( ], E9 I) a
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;4 |: h/ H9 T5 u6 n
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
4 s* i" M+ g3 H+ M& yparmN为可变参数的前面一个固定参数.( G' P }5 T7 D3 n h2 T6 k7 N8 }# G
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
% q. a: o# y, |' O1 A) [* H9 Y2 hva_end(ap) 结束可变参数获取.) D- W: d/ X/ N. U
7 A' Z* R* H. a% [/ R3 P
3)可变参数的使用实例
6 V& K0 Y. ^+ d \
5 c D8 D5 q- r实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
- n# Q3 ~8 j0 b3 d
5 U6 {) E/ y) g$ B; K9 c2 R#include<stdio.h>
/ g4 g$ l0 M& @6 P9 N0 L#include<conio.h>
3 t6 x: j" e: k& p5 o0 c; ~- N! K& W#include<stdarg.h>- G( x; O# D4 Q( k+ o' {) _$ Y' M
void tVarArg(int num,...);/*num为可变参数的个数*/! [1 `/ N2 t( [
int main(void)# W( G" R. o+ ?1 e# M( |4 X6 X
{
0 T, k; g3 ]. Y9 J8 a/ @9 Z. E! d5 e+ Sclrscr();8 i9 J" i1 s% f0 ]0 @( G
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
( K5 k D" l% x9 A% ktVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");, `" m/ S, g+ e" Z
getch();5 |& U; v+ Y: S( Z$ T/ w3 W0 E3 Y0 g
return 0;! Y% c- g- V- m$ L5 p3 U) Q* Y
}: z$ S2 \7 l5 g7 v" R9 x
void tVarArg(int num,...)
; g2 X0 }/ g5 O W( M; {+ ~{
5 l% M. |7 z4 ~1 d' G" o9 qva_list argp; /*定义一个指向可变参数的变量*/: v" _% z; N/ s/ _" m, y% |
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/% Z7 K6 d" g2 Y
while(--num>=0)
, |4 x: _$ j- V5 R printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数, o$ f: G) ~/ s. f+ C* q5 e
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
0 E3 r+ [! R; bva_end(argp); /*结束可变参数获取*/
# j) P: z6 ~( w6 i# d) [return ;* s$ h1 k3 O* T7 D' ?
}
8 V, S# p% z( N' k l" h7 n, z
& u% u$ J4 E6 D! d6 l5 ^% c( z4)可变参数的使用需要注意的问题! ^0 C; y. N. ~1 u; z$ K% [0 {
9 l: ^ D- W1 Q; @1.每个函数的可变参数至多有一个.
4 |& N" N: Q5 f2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.' U7 B) Z; p$ M1 {
3.可变参数的个数不确定,完全由程序约定.0 N0 e3 M5 F: h
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
- C g: Y* U, R: o8 t* ?而printf()中不是实现了识别参数吗?那是因为函数
* ^# T G! h% \* t& ^printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
+ I$ ~; k' s% l# z, u的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 - I+ S/ G6 v$ H* m& d9 a) H' ]* G
过在自己的程序里作判断来实现的.
0 n% _9 }- C4 U5 Q/ b5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|