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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.2 o3 W. E9 W: ?
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.* b( t- m) o$ Y( n$ C6 Y' t& i: h
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不* `) |+ N# a, K8 {6 w
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有5 [8 P+ z( Y% b4 U6 v* [7 h' v, u
实际的名称与之相对应.% A% |* ], i: [% M/ H: i0 r
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
2 w" H- b! C) z$ H: O& ~8 r1 N然而,更多地自由,同样也加大操作上的难度.
4 ?% m2 h# {1 Z; ?& i5 e- e以下就对可变参数的几个方面作一定的介绍.
- N A# d3 @) R- a H4 k% X
! S L+ }) P, R1)可变参数的存储形式.
; O8 K& q; y0 S/ A% ~& i# `$ z% o6 w4 f7 t, S( D
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
6 y8 ]3 P. U+ |. M% ?存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
; x+ S% q/ C8 M& | \1 l' D/ J在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
; `' W V- ] K/ B+ R8 y, {/ ]这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.. u" o; f5 n, o/ o0 G+ _
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
; r9 |" I8 T- P$ j J! b! N; G栈区:
! k* S0 M4 x9 O% m8 t, e! x: \) `
* r# U- L& V! o$ p/ `- v|栈顶 低地址
! |5 o- Z6 G* L8 Y# k6 m+ `' a
& Y. L/ q/ c5 L|第一个固定参数var1, B ^# |& V- u7 [. I2 w9 s* y5 Y9 H5 _
|可变参数前的第一个固定参数var2
. T0 r4 q+ \. r! y0 a|可变参数的第一个参数7 M" c# T* F6 ~- G- h+ X
|...
( r" R W) A2 q# y! W|可变参数的最后一个参数
' c ~8 V J8 `+ F( J|函数的倒数第二个固定参数var3
) D; {! Y2 @: F. Z7 s/ l* p|函数的最后一个固定参数var4
( g, a c4 i6 }- M* |! K$ D|... f6 P$ y" n% c% M4 b/ O% D: `
|函数的返回地址
g, N5 y$ M" ?' O# i3 d|...
2 C) x7 ^' k0 q) {. O|栈底 高地址+ A; a! x! ?* G2 Y5 A2 x( L; _
7 h0 p3 A( T( L/ s3 @
2)使用可变参数所用到头文件和相关宏说明# y. h5 i' n0 q1 `! [4 l! D
$ W" B# z$ ?3 r, h2 M
在此,以TC2.0编译器为参考对象来说明.
: |. h4 a: U, l* } H$ N" f可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中. p9 L* q7 \* k" w- t; z& ]
此文件为:5 \4 a$ c) a& U2 r' L2 H
/* stdarg.h
5 D* T' D L- u, ~: ~, G
( y7 Y. ~8 j6 o0 Y" G C' nDefinitions for ACCESSing parameters in functions that accept
" x4 D* h. E- l% i* T7 j5 J! {a variable number of arguments.
& t" D2 I' D& @6 L) I7 g3 u
( G4 W+ N% f3 L. uCopyright (c) Borland International 1987,1988 w; o, B0 r2 M& y: J* q: [9 ?8 T [: @
All Rights Reserved.
! ~ G5 O9 J8 z6 ]% W/ D; Z*/
V0 A4 o6 u4 B* ]/ C3 j7 S) J" N% H#if __STDC__
' ?' f- I: {7 ~. d0 C* w" v$ e- r#define _Cdecl$ N: r; N4 \2 x2 O$ q
#else# f2 H! u7 @' H" g
#define _Cdecl cdecl3 F/ \ T' z o( s. j4 i
#endif0 H: I3 w/ g' Z2 ?3 N; T0 F4 C0 G1 ~2 a
2 J' @9 ]. R' }: k#if !defined(__STDARG)
0 s6 c! P4 F* ~) q4 s3 j3 g8 G6 y#define __STDARG7 L& E* H% }/ }
3 T0 `! { {0 R8 m
typedef void *va_list;' G2 b1 m# I: ^
2 ?, o! c& E) v& _9 ]/ D: \3 H#define va_start(ap, parmN) (ap = ...)
' {3 I# X8 y) a: s# _; @- ~/ v' _#define va_arg(ap, type) (*((type *)(ap))++); s" m9 g" i4 F' G& Q r/ b4 M+ c
#define va_end(ap)0 j- |. l6 E2 V/ `* G. m* `
#define _va_ptr (...)+ d$ V" w* B! j8 Y$ b
#endif9 T; z. B2 p: v. J
, \; V& X& `3 @: ^8 ?8 }
以上为"STDARG.H"的内容.9 P8 c1 T1 \+ _: T
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
3 n1 V: K# `( }* Z8 j0 rva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
3 W6 }) M2 n& {* @& Q- PparmN为可变参数的前面一个固定参数.! q3 r ^1 B& F3 w$ V
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.: l* E0 Y1 j5 b! y5 P0 h
va_end(ap) 结束可变参数获取.1 N! [: }( A% a4 V3 {8 ~$ |; `
e6 `! b3 j8 J' |3)可变参数的使用实例1 Y% G: m7 `) Q# {
5 m; @3 E8 |7 f( V# o9 z
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.2 k1 ]$ E E5 I- U; H
& i6 C/ k$ k8 W8 p F. Y6 `9 {#include<stdio.h>
: U+ K( m& N1 T0 c#include<conio.h>5 L( z1 W4 T9 J
#include<stdarg.h>
. q* X; B% G& B% x) n( Qvoid tVarArg(int num,...);/*num为可变参数的个数*/
& q0 ^. V5 k5 G$ mint main(void)5 {- o. M% ?- x [
{( @; k" j' ~- [4 K0 n
clrscr();2 t- k$ J% U8 w5 A
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");& K' W, a/ f" k! A" C' Q8 U3 y
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
3 N2 a# O/ N' Q. f1 pgetch();
& b Z/ U2 A. C' H; \2 o. l# sreturn 0;8 @% e) y+ \$ [; V& p" p1 w9 n
}
4 o2 z& u' P. i( Uvoid tVarArg(int num,...)% S- Q( v2 H) n) f2 _
{
* n. z1 W# u: p( _, K" |/ Wva_list argp; /*定义一个指向可变参数的变量*/4 O3 |+ N5 D- z
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/5 N9 B% u2 i/ ]
while(--num>=0)# N1 Q) g# ^0 Q7 [+ i% V
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,5 A5 A" Z1 R4 J/ \9 o
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
8 M: [: W8 M: y) `' rva_end(argp); /*结束可变参数获取*/
8 K$ J! e; Q5 ^5 F0 lreturn ;& ~4 c' ^1 t* h4 b
}
) @3 W3 G' y4 I' h2 j. M: Z/ u; U
4)可变参数的使用需要注意的问题* \/ ^$ P* U7 F" U4 s
1 E7 F5 g. Y6 x8 O3 W2 C% j1.每个函数的可变参数至多有一个.
5 @3 a! T" ~9 I9 y0 k2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
) m) [3 c% _! p! p" t3.可变参数的个数不确定,完全由程序约定.# ]: C5 X& A, V/ {( R7 Q
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
& u* a7 t( H0 h而printf()中不是实现了识别参数吗?那是因为函数
4 ]$ _- @% f4 ]' y' H% x& ~% I8 Y3 ]) v3 qprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg ; T8 v: T d, F7 O3 y! ^
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
% n0 i) L* i ?5 C* @" n过在自己的程序里作判断来实现的. 8 p3 b0 p+ |9 D+ @- z% p* c, W
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|