标题:
函数的可变参数详谈
[打印本页]
作者:
zw2004
时间:
2008-1-21 19:42
标题:
函数的可变参数详谈
可变参数的英文表示为:variable argument.
3 d2 h6 l: q. G2 m$ ?
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
; D" ?* ^0 j$ x3 d. N
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
: X) [2 L) e, K1 l$ a& d
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
0 |' s2 j' b& p. j$ H8 y3 b
实际的名称与之相对应.
2 i4 D9 n; R$ r$ D
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
2 _/ } }1 X$ |! g
然而,更多地自由,同样也加大操作上的难度.
w% P( r8 J, P" X+ B A
以下就对可变参数的几个方面作一定的介绍.
; |$ J8 r/ ?1 ]
+ {5 q6 ?7 c! ]( @. w! W
1)可变参数的存储形式.
. x; ~: ?- k; c
! }9 n( Z2 W0 U* `4 e8 |6 c3 ~ _
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
9 Q. k4 n+ d0 s2 ^8 q# ?
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
/ g, x7 w# E( S, J$ u1 G( x# J
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
; {# J, E1 _* Y/ m% n* l& x6 Z; T
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
Z3 T7 \4 E* X( [0 p
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
: G% Y# S! k( B9 w) D% W
栈区:
1 w8 d5 t# k5 Q. f& W: s3 O
5 w( L0 N9 \8 k& Q2 {4 E
|栈顶 低地址
) h# l, R- }2 S* ]/ {+ Z
/ Z7 H b" W/ P" e! { q* I
|第一个固定参数var1
- X% J0 ?2 {6 z4 r
|可变参数前的第一个固定参数var2
& h$ F" _: O& O; y+ \/ F# l, b# g
|可变参数的第一个参数
3 r* |" g/ _( U0 G: e$ ]
|...
! h; a% @; d$ f' [1 K) Z8 a8 R, ?
|可变参数的最后一个参数
7 V% g! |/ h+ L7 n2 p, T v& ^' x# ]
|函数的倒数第二个固定参数var3
. e) ]9 ]0 j% m0 I0 d* _
|函数的最后一个固定参数var4
" t( G/ V$ V4 K8 c2 z6 U
|...
. ~' o& C7 d0 r9 A4 u5 g: U
|函数的返回地址
. `8 W! J2 b4 c+ p7 Z( W
|...
* C' e! }5 l) `" o0 P; ]: R& u
|栈底 高地址
" M1 u) q4 Z- I& t* y
; [$ L9 E! w" w2 g2 O ]. i
2)使用可变参数所用到头文件和相关宏说明
+ ?7 i1 O6 R7 K3 M
( E1 [) G3 Y1 n' H' ~% T, m; T
在此,以TC2.0编译器为参考对象来说明.
' y0 \6 G4 u$ p* O. W: Q( |
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
; A3 C- T/ |; c- s$ Q
此文件为:
8 w3 }) W1 {3 Z* d3 `1 R% f
/* stdarg.h
+ N9 `& J% a# l3 F' L& f, m; a* S& l! d
0 L) N! G5 H1 Q7 ^! e2 R6 K
Definitions for ACCESSing parameters in functions that accept
" j: s4 S7 \) H/ }" G' g o
a variable number of arguments.
4 r' [( L C& A! y) O9 q" `9 G
4 {/ N9 d4 F0 |: m
Copyright (c) Borland International 1987,1988
3 m2 |; A; {% I5 I' `3 A
All Rights Reserved.
- b, {) q! u6 c% Y
*/
2 Y8 [, Z9 W2 r- t2 e+ t4 M
#if __STDC__
8 S% i! b+ d3 V4 ~) ]$ W! C
#define _Cdecl
2 H' p5 K5 J* J8 Y; T0 d7 y5 k8 a
#else
9 s6 ?& @) y2 Z0 P* a0 z/ c
#define _Cdecl cdecl
7 E C' ~# |+ E! y+ F
#endif
( P; {7 [1 t/ M* u) I
# S) C/ z$ i2 f, z1 d' R) x
#if !defined(__STDARG)
8 `9 p; P9 D4 {2 [9 K# J
#define __STDARG
* }9 m. }+ H8 m" `* n- p
) `! u r5 C$ P5 e. Y5 L7 x
typedef void *va_list;
* K. R o+ d: T
/ z# W4 T6 D( b# a) i1 _
#define va_start(ap, parmN) (ap = ...)
8 g( `- ^" H p6 H2 Z D6 L
#define va_arg(ap, type) (*((type *)(ap))++)
X- y" C' s" x L. ]9 E
#define va_end(ap)
/ Z# g8 x0 M0 Z, b; e2 l$ ^, h
#define _va_ptr (...)
+ W/ T9 y- d2 @* {5 E2 U4 D
#endif
. I+ Y+ q I2 s5 t1 Z: q( A9 d
: F$ i% @! T' [
以上为"STDARG.H"的内容.
" ]3 n4 N* j5 [; M" k
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
( c( U% W: ]" k; v* R& o
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
1 ^2 a; D6 \2 ^" a3 S8 @
parmN为可变参数的前面一个固定参数.
. a* m; P# [! A5 o, ~0 A" A. a; P
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
! s4 `9 t5 d5 O* S( @# d
va_end(ap) 结束可变参数获取.
4 f9 }% }8 Y5 y+ ]
, f/ d1 ?9 x5 A$ g! J @" ^
3)可变参数的使用实例
1 d# k0 y h) w3 R+ y9 D1 E
6 N! g, ]4 n2 y
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
3 s& w* i4 P7 a7 o4 Z: s
' Z# s+ g( I% b# Y
#include<stdio.h>
& c* i5 q! U; v& g3 l& l2 E
#include<conio.h>
1 j' J' C: H Q. j9 p! x3 d
#include<stdarg.h>
, J' D2 h8 F1 z8 g/ `7 @ g
void tVarArg(int num,...);/*num为可变参数的个数*/
" y& D4 C. K3 R! \
int main(void)
7 @# w1 j4 f g1 F
{
1 o4 f/ c. G4 u S$ f
clrscr();
, O; X- l7 |0 H5 ], J
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
- {+ n2 L! r+ g% A( v
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
: s- r/ F/ u4 B5 }
getch();
4 k4 i: J, N$ F! h. O0 r
return 0;
( p# s1 G0 ]7 r* n9 f6 l8 s& c
}
! M+ H; ]. b% n6 T) V7 @4 P
void tVarArg(int num,...)
1 e3 T& f5 X" o3 l2 T: G
{
1 l0 |- X$ H0 M
va_list argp; /*定义一个指向可变参数的变量*/
" ^& Z% C# S' |' G8 Z) Y
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
! I! w: }, v" v6 Z% }0 E
while(--num>=0)
6 }6 ]& I h6 n
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
! p: u; j0 z8 \$ [
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
, ^1 }$ a: X" R% h5 o
va_end(argp); /*结束可变参数获取*/
- N6 Z0 _0 |! q) m2 Q( i3 A
return ;
* K7 k3 W( b' C
}
$ S$ d3 `. c! Y+ i+ n2 J+ B
6 L% _1 |+ Z' X D5 D$ `- |
4)可变参数的使用需要注意的问题
" A f, j8 j, B- [$ `# C% x
& H% n# p. K% c$ y
1.每个函数的可变参数至多有一个.
! K' N. I% Y" h1 h5 f: G
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
6 s! m4 n9 x- I
3.可变参数的个数不确定,完全由程序约定.
. n, `' Q# ^, z. `0 z: A
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
; g' M4 N, L: [
而printf()中不是实现了识别参数吗?那是因为函数
& r K( k+ O9 s; m6 M, D9 C
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
* {# S( b+ s6 {: o0 |
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
1 r1 K* i7 w) L8 ^
过在自己的程序里作判断来实现的.
0 H5 ?0 ?$ G/ ^. g4 k
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.
欢迎光临 捌玖网络工作室 (http://www.89w.org/)
Powered by Discuz! 7.2