标题:
函数的可变参数详谈
[打印本页]
作者:
zw2004
时间:
2008-1-21 19:42
标题:
函数的可变参数详谈
可变参数的英文表示为:variable argument.
4 X2 V# j( H$ {- T9 K- q
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
% N. S+ I& L5 h% z2 |; \$ {7 t% g
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
B; @4 Y4 L5 }: y9 f- f& H
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
: S& X2 s1 \8 d9 V- E/ e0 k/ ]1 f
实际的名称与之相对应.
% l5 Q; P" Y$ N6 c# s+ t5 n/ D
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
/ k1 r. e% M6 H% I6 X/ M
然而,更多地自由,同样也加大操作上的难度.
1 u! r3 l! n% J1 o- x/ C) g
以下就对可变参数的几个方面作一定的介绍.
4 ]* n2 I& u6 z- ~% Y( t
- m: }/ _, a: T/ I: \/ E* J0 q
1)可变参数的存储形式.
0 `8 n$ Z8 B! H: p( K0 e
- m+ _9 e8 @# l0 C, c Q
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
& _+ T7 Z! J. g o
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
8 t- e3 I5 {6 t, F
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
& t s3 n5 T5 K& f( r, e9 c
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
; B' {% B6 s/ T) }0 q$ R3 f9 D3 R
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
! l1 i$ _4 Z9 g* w
栈区:
; R. T+ r x }5 V
6 O" W1 y; X' K1 N7 d
|栈顶 低地址
8 n/ M$ R& i+ \8 B
: E# p7 r: c6 ^3 d
|第一个固定参数var1
+ Q. f3 B4 n3 z
|可变参数前的第一个固定参数var2
9 {) r6 O+ @5 B! e. f+ f; C2 q
|可变参数的第一个参数
7 V4 F- d: B3 o7 \, x7 c& w
|...
+ U6 T' n8 x" G4 n% k) D: x
|可变参数的最后一个参数
/ Q/ s4 ~$ \+ C% }1 Y9 s
|函数的倒数第二个固定参数var3
4 s. O/ M- ~# A5 X4 B
|函数的最后一个固定参数var4
* o7 `' x0 M' x I! }
|...
, N, o9 D" T2 U) }) Q6 Q) a+ c
|函数的返回地址
+ t! T+ H" {( M( f" G7 b
|...
9 m: Z6 j9 u# ?( @
|栈底 高地址
6 i1 c. x2 }. X
5 p' w* g- n1 o- v
2)使用可变参数所用到头文件和相关宏说明
- ^. \* F3 n4 `* m
4 s- t8 p: l2 u# \3 q: \1 k
在此,以TC2.0编译器为参考对象来说明.
+ Q- `+ t2 z7 h
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
4 E- G# {9 s% a7 y; m3 h
此文件为:
) W+ x4 [' Z) y+ W$ l2 Z
/* stdarg.h
4 [. B( q8 Y3 `+ d( k1 j6 X5 U
1 ?3 c0 ^) e& ^1 m( }# H: j
Definitions for ACCESSing parameters in functions that accept
( ], y0 \) H3 b, T
a variable number of arguments.
# ^$ y- t, T+ t5 Z4 P5 M4 k" G' y
& c- M" @" K8 T* k
Copyright (c) Borland International 1987,1988
i. V' N0 u0 y7 K0 {
All Rights Reserved.
, [' j. h- z& A% T
*/
/ |0 |" I1 U3 }% q/ m% r3 d
#if __STDC__
; ^$ p4 G8 J' b- H4 u
#define _Cdecl
( B' i% G6 L1 V
#else
9 V( X! I6 t n+ @! m; y
#define _Cdecl cdecl
" d; \& p( u. ]0 C6 D7 w. L2 `
#endif
+ t9 L! v- V2 b) L4 p$ W# a! h2 E
$ s/ U% `% J* S) M6 {# u" L
#if !defined(__STDARG)
3 w- U# y) B7 W5 [+ h# w
#define __STDARG
/ ]+ E- r/ y* r
& d4 Z# c0 e% d/ q* w/ A
typedef void *va_list;
! d1 ^6 T5 `; G8 v* K
1 P! I7 `2 ]; Z6 {9 q0 p
#define va_start(ap, parmN) (ap = ...)
( G" J& R8 T( b& ]! E. _- [# l
#define va_arg(ap, type) (*((type *)(ap))++)
8 R5 G u+ e7 I' f( D
#define va_end(ap)
" l c( m1 x5 m3 M
#define _va_ptr (...)
. i0 Q8 U& k9 S; m
#endif
9 i5 O0 c" L P/ d6 i7 f0 |& O
3 H$ _8 m' Z8 ?: W! D
以上为"STDARG.H"的内容.
5 U2 v! F9 f; x; _/ C7 I
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
1 [* i6 `- ?3 a% }" y3 b" q
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
6 ~ X: T: L. c
parmN为可变参数的前面一个固定参数.
. C) Z3 W# J9 k2 W) ]( w
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
7 p: X Z% D! k- m" F1 e
va_end(ap) 结束可变参数获取.
8 z4 ~: T/ Y+ E R1 q$ _2 G
8 Y4 }( ^ u/ h; ]8 M. t; Z
3)可变参数的使用实例
# ~2 K/ o8 j% T1 g+ @5 T' \
% z+ J# p& `7 ]( l: M, f
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
: P7 V6 [4 F X$ G6 X- b/ {; p3 Z
5 v# d U" g# ]" d+ ^; O
#include<stdio.h>
+ D# f3 X( F0 H( I3 U8 o9 G3 w
#include<conio.h>
, s: i# T$ L4 }0 q7 j2 s+ F
#include<stdarg.h>
( B6 R; c7 { V, \* d
void tVarArg(int num,...);/*num为可变参数的个数*/
2 d2 X- S/ N8 p: J0 }
int main(void)
+ @) [: T: W7 ~! P
{
6 i8 j* x7 h5 G' \5 {2 ~2 K7 H0 M, o
clrscr();
& ], X' z9 C8 c5 x
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
* E% c) S; @* O; o& h; F2 U9 j6 P
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
) C: D; H0 D \/ `: s. J( ^
getch();
! }* u1 |. q2 o0 G* ~+ m0 \
return 0;
E+ W' d9 ^- _6 V8 |3 h; l
}
7 a. s0 u% o8 ^
void tVarArg(int num,...)
( A+ K5 P2 a: @2 _; `; ~: {1 ?
{
! Y8 F9 \4 E. X9 R0 H
va_list argp; /*定义一个指向可变参数的变量*/
: K" C# q, b0 {. S9 ?8 v8 f
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
0 e6 _% n' G6 `. w c
while(--num>=0)
7 ?( R* ?, d+ I o% ?
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
' k L! K6 b$ z6 p7 ?
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
\) o1 b1 P# F6 O
va_end(argp); /*结束可变参数获取*/
4 p7 q4 ^: ^5 M( r' q b4 E
return ;
! ~1 S' G3 s" Z) ?2 [" U* q$ s
}
% {5 V9 _' A q' T0 F
5 i2 t! m* u* }& h
4)可变参数的使用需要注意的问题
: [6 C6 K$ F/ ^; p+ z
* \0 T% h" H4 n) }/ F" e4 U+ ]
1.每个函数的可变参数至多有一个.
7 L6 ~4 M: y b, y8 W
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
& i' l+ _7 u; {* |
3.可变参数的个数不确定,完全由程序约定.
0 s6 u. U4 q. i& r& D& s, |7 H$ Y
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
% _0 v- Q4 [0 u( l6 i# ?
而printf()中不是实现了识别参数吗?那是因为函数
: z. l4 H5 C: A$ ^) y! ?! k! |. p
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
4 `. R/ l! N' M: T @& Y* T! d
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
$ X! [3 G7 s2 l5 F8 j) y- x% N
过在自己的程序里作判断来实现的.
/ B5 F+ k5 S3 }) H5 U
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.
欢迎光临 捌玖网络工作室 (http://89w.org/)
Powered by Discuz! 7.2