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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
" F2 ~" Z9 ~+ Z它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
4 u, G1 D# z7 E0 A, o可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
8 Y3 ?. J: X4 I9 S; G# v定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有' |$ e/ [5 a7 H9 K5 `9 W; D' o- z- ?
实际的名称与之相对应.- e6 M4 B& {& v8 V5 A/ U% e- N5 T
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
, \/ h' y1 f7 _' y7 t然而,更多地自由,同样也加大操作上的难度.
+ L+ K% V2 \# x8 p3 V7 _: j以下就对可变参数的几个方面作一定的介绍.
' ]6 |+ z+ z) z& e5 \7 p+ F; {3 M* G: l7 F2 c1 X" y: t
1)可变参数的存储形式.
6 a; W: g" K+ r4 ]: h! Z9 R0 X: U0 n& y/ T% F7 F
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,! P% h% `8 h. ~( e
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.- k$ e H5 s, p
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
8 `2 U2 ~' z$ o0 u3 \8 i! B! N这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
$ F( W4 ?, F' x1 M" }! m9 h+ S% V& P因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
* p' E# R' o j" _* R" S* |6 V& L2 e/ {栈区:
- r5 c/ Z8 d/ Q; W8 E, m) }0 ^! b S5 k: v5 H0 T& Y' p3 H w0 O
|栈顶 低地址3 |( M8 [0 [5 w) ]
) C! H, Z$ Q1 U- F! i- O8 c! \|第一个固定参数var1" ]# G( v( p" u
|可变参数前的第一个固定参数var2
# L( r* q8 `! u4 E|可变参数的第一个参数
+ v1 V) J. T& D; c x* f|...+ u. X1 |( \, H3 L/ b
|可变参数的最后一个参数
0 l, W7 D" N% o3 U! ^5 {1 T|函数的倒数第二个固定参数var3
4 Z# n5 i$ ~, ]' ^) C|函数的最后一个固定参数var4- \; B2 i6 ]( A: q
|...& u i; N9 F2 c9 w
|函数的返回地址, T; M( K" S. y
|...' G0 f ~4 q8 o9 {$ ~8 |- }
|栈底 高地址
: Y! c/ t7 I5 ~* S. k8 s5 M2 v* l7 Q1 c5 I9 Q/ |$ [; q8 n
2)使用可变参数所用到头文件和相关宏说明
' S# x' @# v' k, J: ?; T1 T
" W/ C) H; {. n/ a8 e1 p在此,以TC2.0编译器为参考对象来说明.4 b/ e* l( _9 P3 R. v# b
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.2 }$ D8 l, J- L' C/ L
此文件为:! z* \' A$ M: @+ p: s; F$ u' K: J4 C
/* stdarg.h, k" q/ y: T, m( O7 U3 t9 v6 O3 r
8 l j# {- m& A. r- XDefinitions for ACCESSing parameters in functions that accept# Z1 Y. H1 ^+ h& |5 R! |* \$ T1 J
a variable number of arguments.
& H5 _' h2 [) i
8 \1 \, O3 q1 V, x2 nCopyright (c) Borland International 1987,1988
$ S( j+ r* W P: u* VAll Rights Reserved.( x/ E) U0 }* r4 t- p
*/6 ]# T: {2 M; r7 }1 ~
#if __STDC__
: p! j% v, H% ?: I#define _Cdecl: O, x5 N9 i& l( o4 }) T {
#else
9 P; k$ O" b! _) m#define _Cdecl cdecl4 J* j H0 i0 T& V# M( I
#endif
2 X' u9 ^8 C$ s) ?. V" d R$ X) c! G9 j
#if !defined(__STDARG)4 \% U4 x v3 }; Q+ `
#define __STDARG
& J+ Q' m: X9 m7 X
7 H5 U$ j5 A. `typedef void *va_list;8 o6 ^1 x! ~; s3 v
5 _! s$ L5 N. _" F( H. a#define va_start(ap, parmN) (ap = ...)
7 h q7 Q! s, n" ]#define va_arg(ap, type) (*((type *)(ap))++)" G" l' d6 R% H; r" s
#define va_end(ap)( F, v3 A |8 M) ?- @: @
#define _va_ptr (...)
% H r; V# F8 X#endif
- A/ ^ x, U b# ~, c3 X; B* Z( v. O9 n
以上为"STDARG.H"的内容.( G8 ]) U9 L: z( D1 p8 z
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
$ D$ y6 K3 o& A$ n( Ova_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,+ @6 {1 |- \! D" ]
parmN为可变参数的前面一个固定参数.+ g9 n! [7 A V! J& ]% f
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.4 A& H" G0 _ m6 t7 \# V
va_end(ap) 结束可变参数获取.
$ ^; k/ E& M2 D3 ~
5 N3 p7 w9 ?/ D8 o: m3)可变参数的使用实例8 C# u- `' a$ ?6 B) Q
: Y. }3 i) b" c9 H5 s) R- v- [
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
- ?" d- v# W) V" C7 t) E7 h+ S( c
0 g) K# }3 A- e" ~7 r" F1 Z#include<stdio.h>
. a. i% |9 {' P#include<conio.h>
) U1 S; c. c8 h#include<stdarg.h>
& z5 }" G$ u4 g, S) Lvoid tVarArg(int num,...);/*num为可变参数的个数*/) Z* }# o; D. [
int main(void)
: }4 f# B8 i8 [# h{
3 p- v: ?$ w" b# Fclrscr();* I6 v( g. ]6 h' O2 [- i- |
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
2 G- W# H! r, }, DtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");- u$ g/ @1 J% |* ]* l
getch();
7 ^8 J# d# l: Q4 j6 ireturn 0;
' j V& L8 \+ c U: p! M$ i}; m: r2 x( q. ~" V; y. C0 F
void tVarArg(int num,...)
! Q! F% U1 J/ i* G) C{
& i, f, ^4 S1 [* s9 A; v5 j% Mva_list argp; /*定义一个指向可变参数的变量*/
9 |! M- X1 a, }, P: |va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/" f2 v5 x! X# ~. D
while(--num>=0)
: @2 e, [% E4 z; L) ^, g+ [ printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
& w7 c. h0 E v' [. Q9 [/ r! e+ s 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
3 K; I( d9 N k+ Y3 r7 pva_end(argp); /*结束可变参数获取*/
- x- a4 F& o1 g7 |( e0 qreturn ;! A9 ^& P4 M* v6 J$ @$ j, ~
}
1 b5 y4 v2 G/ d+ S' }' z D% Z( N, R) N# i4 G! q" y0 [ t
4)可变参数的使用需要注意的问题
6 y% p! o& m9 M' e1 U. g/ y+ z4 J
- z+ t7 u3 t: B' u1.每个函数的可变参数至多有一个.
/ N# K+ C% F/ ~9 l1 r2 |0 X2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
+ P; s+ X* P6 g, z& K( r3.可变参数的个数不确定,完全由程序约定.
; O3 E7 f1 B* l" X4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
7 O& W& P) h# M( S1 P* ~ J而printf()中不是实现了识别参数吗?那是因为函数 ' D: T# c/ o" |3 X$ \; W
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
& C/ r' S' P7 x! H的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
$ r9 z- i" I4 ?6 x过在自己的程序里作判断来实现的. $ P+ Y, u% P H$ C! g. o
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|