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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
5 ?; _9 @% E9 x' Z' D/ m它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.2 g7 b! P. e! U" _( m
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不 c" P" q. L# J1 z
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
; k! d. \9 X" ^实际的名称与之相对应.
/ v5 ?7 n. l" i. r5 Y由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
5 D& n* V1 y# u* ~然而,更多地自由,同样也加大操作上的难度.4 A* z# ?6 ^8 v2 b
以下就对可变参数的几个方面作一定的介绍.
' ~& x2 i5 \' q/ e" I) X5 Z( f9 K
1)可变参数的存储形式.% F. `4 r$ l- _' s+ ~) o8 O j
5 U/ a8 ]; z/ U9 P, o& _
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
: L9 p- u& l6 v8 `存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
@: m, p4 \- `在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,# J/ U+ v0 ]5 w& T( _
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
* }( o/ W' Y1 ]7 {5 x" R K- q因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例): `$ o- D( X/ }6 S
栈区:
" e7 {, Z, N: d- y/ P! e- v& @) P/ e x" h4 F$ T4 l
|栈顶 低地址8 R- @9 m" h8 f3 ]' m5 g$ I( G$ Q3 b' w
* X; A+ K; g" Y# N9 M|第一个固定参数var15 f; n$ L! p$ R+ P
|可变参数前的第一个固定参数var2
( \- w i6 m: s; }: l0 e: [. s; J' {|可变参数的第一个参数
' D3 R& W6 z: W2 Q: l1 ^|...
. }7 ?6 `8 e. z( D|可变参数的最后一个参数
3 Q: \8 |/ b y" _" Q|函数的倒数第二个固定参数var3, P& L7 Y$ O. Q/ u1 X$ ~5 \
|函数的最后一个固定参数var4
" I/ {( i3 p# ||...
# o8 w \/ q* G. H|函数的返回地址
" f$ z" ^) a( U0 d ]. b- Y7 P. z3 j|.... {" ]7 e( x2 I8 j& A! `) \" Z9 [: V
|栈底 高地址) l5 A( _$ |9 d1 I& R
) i! ?6 e4 [& Z6 A/ l! S+ h2)使用可变参数所用到头文件和相关宏说明
8 X8 }1 j- w: {% F
. b* ^3 Z2 N4 `3 ?! Z! {& I在此,以TC2.0编译器为参考对象来说明.
. M& ?! Q% t! n4 V可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
3 G' `" O2 V: y此文件为:$ Z9 p$ D; [! B) Y8 @2 G& B& z
/* stdarg.h
/ S0 Z" M# ^/ R9 H
7 i+ l4 g' h' c3 V" y- PDefinitions for ACCESSing parameters in functions that accept
[2 S9 E/ O6 K% Xa variable number of arguments.
, q; w3 k" A h1 u: _2 z
: m) j S g0 j9 ]6 G) @Copyright (c) Borland International 1987,1988# u) C7 i* }6 J1 a7 o
All Rights Reserved.4 v( o. P7 j2 w+ F+ Q. p
*/
3 a$ `. i c8 [, k; @+ u/ ?! a% h/ V#if __STDC__
# D& x) ?' `6 p4 F4 J% T/ v, g1 l#define _Cdecl$ w% S( J. I3 s0 o9 K
#else% Y k9 B) ~% ?7 A3 C6 z8 p+ F; M
#define _Cdecl cdecl
& } N, g% E. u#endif' \7 W ?. H; {8 a" R
; u. F/ \9 G4 W- `6 j$ d' |#if !defined(__STDARG)
2 x; U1 ^5 ~% A( l# `#define __STDARG- h9 \0 s5 X# _" z
+ m& _3 }& h8 D3 |. u e! O& l
typedef void *va_list;- b9 ~" a! E$ G- t5 B9 Y
; ?& T" U1 u% K, i& u0 `- V#define va_start(ap, parmN) (ap = ...)$ z3 U6 f" w8 d, s0 o0 U7 a; G$ o
#define va_arg(ap, type) (*((type *)(ap))++)
; ]. [$ [% g% G3 O' T#define va_end(ap)! Q" s+ c: Q9 y7 y! f3 U
#define _va_ptr (...)" O% R8 t& D% S' y- X
#endif6 U9 M+ |7 `0 I1 [ E: R
$ @* u- w6 G7 K% d( \) `
以上为"STDARG.H"的内容.
0 x+ l, N+ h1 U5 d该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;7 W3 G) G- M: m: N5 ]; T* c6 L
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,* W. y/ O/ l4 z* a
parmN为可变参数的前面一个固定参数.+ l4 R$ u3 `# G3 W5 W* J! r# q
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
0 Y3 a% U' S/ ?+ k$ Lva_end(ap) 结束可变参数获取.
! J2 v- i8 r9 z* K2 [. Y% @
% C$ b- v/ C1 C# J4 s( X3)可变参数的使用实例
1 ^" D0 N$ X& w8 U8 o+ Y
, @, S! L! W7 w& j" k/ y+ z8 W实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
7 v$ v( B+ m% |" j
0 R+ a4 H7 k. J# M* g5 N#include<stdio.h> x7 L% W; W5 d) V$ Q: k* J
#include<conio.h>
( K% ?4 ^7 y% p2 w) V#include<stdarg.h>
, l7 V8 e, i" T X4 C7 s# Kvoid tVarArg(int num,...);/*num为可变参数的个数*/
% e. {6 z0 |3 nint main(void)
; _" w. X' X$ F( {; R8 g" j' y{
/ `; x5 W, S8 d! [* Hclrscr();! ~# Y+ {/ y+ i2 T7 G+ |# T
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
: I x1 G1 o1 ^tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");2 r6 K- {& ?' q; N. L
getch();
7 I% O$ _' {& g3 Jreturn 0;1 N% E8 s! R/ n- k9 H1 K
}3 M3 j8 S$ W. ? x. e O; @) v
void tVarArg(int num,...), I$ r: A& B! I; C4 }/ k
{, z5 I# {) L. |) y* S: J( c
va_list argp; /*定义一个指向可变参数的变量*/7 R( l j( Y% T* |7 v. a/ H
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/7 J8 L1 u/ N. J6 e' g
while(--num>=0)# S1 l/ P( Z/ w# |* @7 C+ y1 p* C
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,( u& I. k6 K$ u( R* l: L
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
8 J5 d4 {9 z# e" M$ o3 J0 lva_end(argp); /*结束可变参数获取*/: [+ d3 N9 R) y0 u$ B* ]& B
return ;
7 }% `& a! z4 e7 Z$ t}& x1 |* q+ |7 K
7 w+ w2 ] `( ^+ t+ f4 b$ J4)可变参数的使用需要注意的问题" Y$ r& u# Q' G* Y1 E
0 L* ]9 K M/ b5 l# M1.每个函数的可变参数至多有一个.
% S1 n, q0 X' i% |) `2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.4 m; Q/ Y5 ]5 d. E \& N! D3 ]
3.可变参数的个数不确定,完全由程序约定. |7 s- z$ H- T9 m0 ?
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
f# Z# Z7 y* r. P8 k而printf()中不是实现了识别参数吗?那是因为函数
0 t- M- b" `' c$ f6 @printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
" s4 }2 U, g$ n) U {的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 / V, B" y4 D) |7 g r5 a8 j
过在自己的程序里作判断来实现的.
" e4 ~! |. v# n. N5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|