Board logo

标题: 函数的可变参数详谈 [打印本页]

作者: zw2004    时间: 2008-1-21 19:42     标题: 函数的可变参数详谈

可变参数的英文表示为:variable argument.
: m4 C" I) q* w6 A: k! @# Y它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
3 l6 n2 ?( q& w( m可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不0 F( h0 e2 [4 B' @
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有4 l0 J/ v* Z9 I1 [6 K( c1 o3 Q! Q
实际的名称与之相对应.
9 `$ l' Z0 x% _- ~; p1 d4 f由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.' x' A1 N/ e8 Y% [7 Y
然而,更多地自由,同样也加大操作上的难度.1 }9 i# Q! q9 p; R' _; P) ^
以下就对可变参数的几个方面作一定的介绍./ h: @6 k' t& V( v

# D3 p3 N1 a6 Y1 l, t1 }4 i/ C1)可变参数的存储形式.. K7 U  i# r5 g. X5 P
% f6 [3 U2 a, G- B( W0 t# X
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,2 ?- [" m# e9 p0 A# ~3 F9 |8 V
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
8 c& x" `  w% g, U8 P- g在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
$ l- I0 [9 D9 c  H9 ^) c这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
5 `& `6 v* J; t) {因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
( a8 a$ u" X6 G' Q1 m栈区:  y4 ~  t, u( m: `. C
5 ]( m, P3 `0 {: P$ P  o
|栈顶             低地址' N) w8 f. a8 U- r3 c  d' O/ N
. v$ p; w" ~8 L9 W
|第一个固定参数var1
9 F% `. J1 h; O2 ^. ]8 G* ^1 i6 E|可变参数前的第一个固定参数var28 t# n8 @1 i2 S  }" `) N
|可变参数的第一个参数
9 r1 ]: O: ?# y9 q6 o; D|...4 ?; u; I# [( j6 X6 [5 x: ^/ ^
|可变参数的最后一个参数" }8 p% T5 M+ ]/ \
|函数的倒数第二个固定参数var3
; u3 J( J! F" E& C8 i5 W$ `7 s|函数的最后一个固定参数var4
/ }5 g8 }1 D! W, [* S|...7 X7 b. y( t2 v$ t# X) W
|函数的返回地址
  I. Z; S  ~1 V. b) z3 f! L/ I|...5 }8 J1 b# W9 q# _' p! ~% o
|栈底    高地址/ a3 L' Z/ W8 R! ^+ F& @
% u5 C% V) I1 U$ C
2)使用可变参数所用到头文件和相关宏说明! H# }- G! ^9 n# b8 [' G
; V+ ^0 i8 h, z6 y, h
在此,以TC2.0编译器为参考对象来说明.; H& C, s* x4 a7 v* Y4 |. m# d/ e7 Q' X
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
$ i# W0 Y1 y: f2 R4 G$ d- J4 i此文件为:
8 [8 Y3 p8 t% P! ?/* stdarg.h" [4 D/ J/ B  C% T

9 a4 h' f1 A4 ?2 o+ vDefinitions for ACCESSing parameters in functions that accept5 U6 T, D7 L; t( o9 Z! l7 m: X/ @% y
a variable number of arguments.8 x1 Z1 u3 x/ ^& A) n4 U

& ?2 L3 T6 D( l+ K  UCopyright (c) Borland International 1987,1988( D& K3 n4 |7 s
All Rights Reserved./ k' p6 n5 F! i1 A) ]
*/, b) h) \: c7 h
#if __STDC__3 J9 n$ }* I, i  W- M
#define _Cdecl6 }# R- L3 {! _- T3 Z! _
#else
+ b( g% T+ ]3 D' K/ @#define _Cdecl cdecl
! a1 w" K# E. M#endif
# x. z0 I; i$ @* K, u! v9 h
, L5 K9 u2 A0 F/ N- x1 H#if !defined(__STDARG)/ z% V) \- n; w+ U6 ?
#define __STDARG! X0 y7 I  z9 @# v* g4 k. z

: Y& p, e: H& ntypedef void *va_list;1 S2 F1 @: l9 p. e7 G2 k. K

& T3 o, b5 U7 G% \#define va_start(ap, parmN) (ap = ...)) n  D! ^+ k8 @
#define va_arg(ap, type) (*((type *)(ap))++)" Z( W- Y4 K: |* j- R
#define va_end(ap)
7 i( y9 E) U0 ^$ g) h#define _va_ptr   (...)- \3 Y5 x3 `3 P1 o
#endif
! K8 }  b7 B) p& c& N" S; V8 U+ F8 }' v2 \
以上为"STDARG.H"的内容.2 ?+ V4 C& ~8 H0 G
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;  q# P5 E, @; a# z. ]1 |- m
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,  s. O( J/ u. s
parmN为可变参数的前面一个固定参数.: @2 l4 w& {. V9 P8 F# @/ e
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
/ g$ c& V& w* j+ \/ dva_end(ap) 结束可变参数获取.
! j: U! W& ~! _; J0 H( ]* N
3 e7 V0 C( O) [6 O; w3)可变参数的使用实例- [$ f2 Y% U- m1 ?* B" g/ X
- n2 B- g2 D2 G/ T* c" ?" S4 v: x% w0 C
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
6 N, {8 ~9 r$ O" o% i
: |: l! Q/ k( U. V#include<stdio.h>8 N  X6 l0 _0 j, e, O
#include<conio.h>. i5 A: \9 b$ w  T; ?8 o% `! R6 L' l
#include<stdarg.h>8 W: S2 G; g. \5 B( T
void tVarArg(int num,...);/*num为可变参数的个数*/
* m8 z; Y% u8 S1 C3 }int main(void)5 Y/ ^1 h  R- K3 W# ]
{% u8 R1 V' I. P! `
clrscr();
% n% F* X" ]' x3 KtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
; t/ N, Z# j8 AtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");0 r; r4 \3 `. j+ l+ ]' Q
getch();
3 I. _- l3 Z. A6 ~return 0;1 ?, y! E1 y9 k
}
1 N' J- K6 j. h+ X- @% D( Mvoid tVarArg(int num,...)
  y8 E% O6 y( @0 Z; @" c6 n* |{  Q# z- j8 h5 i$ H' ^4 E
va_list argp;  /*定义一个指向可变参数的变量*/$ U/ O3 _' i. @" K1 e9 I7 O
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
% X  d" }& ~9 `' Z4 {5 A8 e) Awhile(--num>=0)+ ~! ?( {( j; y3 a% [+ ]  U
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
9 ?+ z; b1 D" ?, J$ n    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
, ^- q8 @/ U6 R4 |, L$ M& |4 j6 tva_end(argp);  /*结束可变参数获取*/
, F) ?/ }( m1 i4 {. j) ?# g2 T+ Ireturn ;* {5 A+ n+ B$ ]$ }+ G: T
}: J$ k+ {: o5 B, h
- Q2 r: p) D" D, x
4)可变参数的使用需要注意的问题1 ?8 S( Y7 a% F/ A; o( q0 C3 |

& D  g4 Z: y; l: ~1.每个函数的可变参数至多有一个.
8 }: T2 y# W  Z- A" L2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
; j/ e7 y+ [( ]! {2 q3.可变参数的个数不确定,完全由程序约定.% I9 d: Q; P( P. B4 x9 z
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
0 k9 \! [  l; ]而printf()中不是实现了识别参数吗?那是因为函数
, l, |: |' u# Y8 r& Jprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
! |& x! `. @5 y2 \3 u3 Y/ }的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 6 s/ ^/ a. R1 f8 b) E2 c4 y0 Q
过在自己的程序里作判断来实现的. ) Y1 v. M& Q- w
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




欢迎光临 捌玖网络工作室 (http://89w.org/) Powered by Discuz! 7.2