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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
+ z- w. N8 D M# L% T" u, ~; v7 S2 s它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
8 G$ n# n5 K/ |1 u3 Z可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
/ J3 _2 ^: S* o7 ?$ e定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有 t! r# o$ k; x3 @% s
实际的名称与之相对应.
" X; t ^, D# M5 O) i2 ] A* s由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.! D# h4 h0 m5 o2 h+ E1 D5 s7 W
然而,更多地自由,同样也加大操作上的难度.
2 o5 T/ w0 }/ @, W& D% ?; ~3 f以下就对可变参数的几个方面作一定的介绍.
9 r, Q/ E0 z$ ^1 q- \4 `/ e) j5 _% C
1)可变参数的存储形式.5 g" m: K% x2 b" k& t) [
* f8 [7 g/ w c( Z2 [4 Q
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
* I) _- Q3 o% o5 v存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
* V( B$ o. G3 E @6 I4 Y- ?在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
5 N4 N' K. p7 T& c0 @- V9 X这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.8 d6 M7 `- f1 m# D y
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
7 \0 m3 ~4 W+ e7 z栈区:
5 N8 `2 k! P+ |' p: W: f' j& g& U: C* P! O; ?. l
|栈顶 低地址
* e5 T: Z8 P; V4 Z" G7 f1 p5 e3 S! X7 ]$ V. ~# @
|第一个固定参数var1
; e% l. n) p8 D|可变参数前的第一个固定参数var28 I! L! L) y% {
|可变参数的第一个参数# C" u* e) z ? Z
|.../ U3 x; Q+ p9 W# b0 n
|可变参数的最后一个参数- O- P4 N* d, n& C8 F* G0 q
|函数的倒数第二个固定参数var3
1 e7 B B! R' W7 w|函数的最后一个固定参数var4
I: D0 r4 r/ c- u q% Z# Q|...
- W/ M' E: [! H+ X' s1 R9 E' B|函数的返回地址' L. I9 B0 H. v* O- [ a
|...
' V' i. h6 e1 X* V. M) P|栈底 高地址
% @! f7 b7 T+ u; ]
( o: \( K4 \' D+ A& t! ^) a2)使用可变参数所用到头文件和相关宏说明' {5 ~" Y2 f- }( Y e
' q' y% d5 X1 k$ S+ ^
在此,以TC2.0编译器为参考对象来说明.
$ ?6 d. a& m% G# }可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
: h4 F3 I2 U4 s) A此文件为:
@6 C; ]( T; k% V! a3 }& g" H2 A* a/* stdarg.h
( O2 \; X: W) j- a2 W) K
& K2 w0 ]; ^5 d2 m# s5 C5 BDefinitions for ACCESSing parameters in functions that accept
( j$ e! c* G) ^# }a variable number of arguments.. K# ?! w5 z. j. |% d) i* R
8 J, |; \8 ?! M. d4 |
Copyright (c) Borland International 1987,19889 T4 x" n, m( H0 V
All Rights Reserved.5 i, e8 L, W$ O8 w. V! i
*/) j. y8 C/ C& D; I$ c
#if __STDC__
; z& R/ N1 @' }1 @3 v4 O#define _Cdecl- s6 { y& s; F
#else
! z6 a8 C; M: r: [1 D. D9 y#define _Cdecl cdecl; `9 t6 h& F9 R2 X! a$ E y- }
#endif
, z+ A2 Y6 L H
' _$ e i, @4 ~, O; h' }' @; X/ f#if !defined(__STDARG)" d2 U% i* ?5 k
#define __STDARG
/ X; v6 ]$ R" V) C( q
7 A* r1 c7 v+ \typedef void *va_list;3 z! P$ Y$ A* K6 f2 I- C: |
$ F; `0 x( `9 }1 l- p, R
#define va_start(ap, parmN) (ap = ...)
/ I% e$ a+ G% c. Y+ ~+ A t6 g& u#define va_arg(ap, type) (*((type *)(ap))++)
9 q0 L n( g( p' D' w9 c7 [#define va_end(ap)- z0 i E% Y0 F, F0 A, P
#define _va_ptr (...)
5 Y$ g3 R5 Y; B" |7 a: Z1 H q; H$ y#endif5 x8 Q% n0 F5 H7 m3 S
& W$ o; c, S' n3 N+ a- ^
以上为"STDARG.H"的内容.. E- D7 z! Q. G3 ^. r
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;$ A# R6 i8 C, _8 t
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,! U/ }- F4 ]0 {8 A2 K+ H/ M; t0 o
parmN为可变参数的前面一个固定参数.1 k* |9 R% L# D# e
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.8 t) m9 R y, j D3 ^9 u( k
va_end(ap) 结束可变参数获取.% [# p6 L, U7 s
/ t( f6 r! ?8 \# T0 p8 E
3)可变参数的使用实例' [6 D4 \. e6 g) t" j5 V7 T+ s
) O% Q+ p0 E( Q3 \" M% q实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
. E; e0 z2 n7 S" ?/ Z
( g6 M) @1 _% D ^#include<stdio.h>; O. `5 S/ g9 t1 N% S
#include<conio.h>
* O6 d X. v1 @" `6 G2 f' y: K) M#include<stdarg.h>+ i: U; n, `' m: f
void tVarArg(int num,...);/*num为可变参数的个数*/+ G; i2 M; R0 F1 ?- Z' x% s
int main(void)& r* V6 E4 U" ?0 v! @- M2 G4 L5 g
{. Q0 r/ M# J- E- ~
clrscr();* o6 ~; O% z4 E3 Z- e0 }1 s
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");8 Q# C4 W7 C) M$ Q. e% n5 w
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
- F+ [+ P* X/ j) z4 O* z+ ~" Bgetch();
, t- }( K, n8 o9 treturn 0;
9 v5 h# a, a" _, R" l}% _7 n( x( C9 \) f; K! A
void tVarArg(int num,...)
9 K, o+ q5 c0 R9 W{
# v1 B( K5 u+ F: P3 Ova_list argp; /*定义一个指向可变参数的变量*/
" I1 L9 J+ S4 A( o7 O0 C6 M+ [. sva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
. o' d5 N. i6 w8 bwhile(--num>=0) C4 X+ j# W# X: m( a
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,0 ~1 H9 e3 b) L4 y$ _) Z6 \, k9 q$ G* x1 [% E
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/* @( ^4 ~" `3 F l) c# T! T7 l
va_end(argp); /*结束可变参数获取*/
. }7 [; O# m. c) Kreturn ;
9 G* ]) v4 B1 l- `. L}
/ m% [; `( w. ]# X' W$ J8 Q
! f& }4 @' P4 ]5 F2 O4 {) z4)可变参数的使用需要注意的问题
: J: D0 R! R- w& Q/ @3 ^9 a! U/ h+ o( B% R4 t' ^6 |
1.每个函数的可变参数至多有一个.$ C8 H5 f1 Q6 c v: C9 [- @9 T" a
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.2 n1 d; n& F p' }) [3 B
3.可变参数的个数不确定,完全由程序约定.! l: o- s- N: u0 U9 [
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换., v+ A) _& [# z- w# L
而printf()中不是实现了识别参数吗?那是因为函数 8 g) O+ l% m5 m- W- G/ L$ R! y8 w
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
7 ]6 Y! S3 ^( Q/ t6 Q6 x的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
+ n u$ C0 F, z5 [0 t5 F. W过在自己的程序里作判断来实现的. 6 r4 s3 u( W6 S( O
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|