返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.6 q8 b9 N7 e7 O) `* }: J
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
3 G9 \# t  N7 g) Q可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不5 r5 K& w  W+ f5 W% I2 z( a' V7 J
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有3 D9 i6 R7 c8 H9 H7 h, Z
实际的名称与之相对应.
% [) ~4 u1 @3 ~( l; I由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
8 F) K% Z5 O4 ]9 i! m然而,更多地自由,同样也加大操作上的难度.
# J3 V* i, @- ]" h2 L以下就对可变参数的几个方面作一定的介绍.
& l# |4 B1 p9 }* s$ }% d' I( T. J3 P% d6 ?% d- g
1)可变参数的存储形式.
3 v5 K# t* r% R6 h; \; W% k2 F4 K9 ]! T6 O
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
; y0 g2 q/ \* ^' d4 b存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
2 [) @+ }9 N7 i& N! L在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈," Q, c5 X% [! L$ }0 G1 D; s1 l
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.7 x0 b- R! x' r( s0 k- @2 ~! P
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):& j& I( k# c( e. k
栈区:  M, ^& _! i; |8 N( S
- j5 G. q% F/ u7 X1 W
|栈顶             低地址
5 \' [: \8 G( @( R* l  e# P$ A  r  g  |
|第一个固定参数var1
, R7 ]8 v; c# |5 s" k' n" X& c|可变参数前的第一个固定参数var2
9 K; k& C  R+ A|可变参数的第一个参数
1 S  U  @/ i8 C( i9 t( T|...9 Q$ ^3 O- ?# A* q7 U4 t
|可变参数的最后一个参数
3 X9 }" r$ Z, ?( B6 _9 t% C|函数的倒数第二个固定参数var3
% v. V/ K8 ]% f3 I/ l% U) \|函数的最后一个固定参数var4
( S( [7 x0 P) [5 K- N$ f- K|...& P( l9 |. b; B  ?9 v2 V
|函数的返回地址: z* W7 ?# d' U. c4 z
|...
8 s1 i0 A! p8 O, J" j|栈底    高地址
# Z4 O6 [2 J, ^" ~0 Y3 F8 E  I# t8 x/ w# b' d5 Y" y
2)使用可变参数所用到头文件和相关宏说明2 ~* r/ R; s! ~. n
6 c  G. Y1 ]5 u$ _
在此,以TC2.0编译器为参考对象来说明.) z* i+ [$ f% Z& t; _) c. i% @
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中." O( k" h* Q2 H9 Y
此文件为:8 L! K8 c0 U" V/ g1 z
/* stdarg.h; G- [3 c& W5 t" E) e- S! Q2 }  P
* }( {' I) s$ A  E
Definitions for ACCESSing parameters in functions that accept
& v$ ?" L. k. c0 `a variable number of arguments.
$ \; f& _6 F* z. I; j/ P& p
! y: o! L& P, A7 O  Y* |# ICopyright (c) Borland International 1987,1988% N( n& P2 t9 a9 e% N
All Rights Reserved.. C7 `" ~: i1 J5 |$ |) K
*/* H2 T* k+ g& z
#if __STDC__
4 Q4 b- B1 |2 T9 F: @/ h+ X2 H# _. K#define _Cdecl
3 G7 u. A6 o7 u( D#else
/ U& S# E: J$ {. O0 K  {#define _Cdecl cdecl
4 j8 A7 ~5 j; M7 }#endif
/ i6 P$ I1 ~" ~" O& c
2 C3 \/ i, u( W3 R5 _#if !defined(__STDARG)/ V: o$ B( P' w4 X# T
#define __STDARG0 v+ N; t& k+ I1 ]& A
' k, L$ T8 ~3 D7 e0 v
typedef void *va_list;
) J+ _2 V0 p5 o2 @/ j" \4 D$ \7 \" ^2 w6 S6 I( T0 k9 p5 H
#define va_start(ap, parmN) (ap = ...)( S+ W9 L4 v1 s# J0 S4 f; t
#define va_arg(ap, type) (*((type *)(ap))++)7 ?* j' J' @* T$ L
#define va_end(ap)
: Y" b  \7 ]+ {% K3 B/ @) ]4 a: j#define _va_ptr   (...)
; T- `; j+ r9 y0 q4 b3 y% }  n#endif
* e( s$ a8 l4 y: u5 I. ~7 b6 i4 C  B* P: m1 X' k; ?9 K
以上为"STDARG.H"的内容.! v, s* b! U2 w* k& G: m$ t
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
7 A& ^& m# a$ u2 Q( S4 jva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
5 ]* L) z" ^- `( T2 l0 h6 iparmN为可变参数的前面一个固定参数.) e5 s" J0 M8 k
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.8 A0 }) {; W6 G# Q
va_end(ap) 结束可变参数获取.% h3 E$ X9 g* O- m0 M3 G
" X4 l) M6 w/ R, j6 V
3)可变参数的使用实例  k1 H# d. {/ `- B  B! {

9 \5 J0 N9 d3 g4 @) m6 E" r实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.8 R$ G2 J" y5 d9 y

- G# a( O  V6 A6 E& }% v! [#include<stdio.h>2 R7 @0 h$ @/ f* R6 a4 K
#include<conio.h>, v- T, |9 X! y# I( f0 _9 }! g
#include<stdarg.h>
- \' |, l, }  D7 T" W! ivoid tVarArg(int num,...);/*num为可变参数的个数*/6 [, [8 P( l5 w( t. Q" ?4 V( s& }: h
int main(void)/ d2 j6 h) \/ b' E" x' D, [
{/ }$ ?3 Q6 ], O) C9 u& G% p
clrscr();  [( ?2 c* G& @8 p9 j
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");) H: I9 d: q1 @5 n! T
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");2 y2 X+ K3 c0 c, ?2 C9 `# S% Q
getch();2 d5 j+ g# _! R" ?
return 0;
# {) k  w/ h4 d; ~1 H" ~}
, }" ~- t0 ]! `9 L/ X( a$ Rvoid tVarArg(int num,...)
' ^' u* W! M( ^) @+ c: B1 S{
2 {8 E- M+ O! u' bva_list argp;  /*定义一个指向可变参数的变量*/
. G# q8 x" {% Y" k2 a# s! Uva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
' U% d" W  B) Q- kwhile(--num>=0)3 ~2 ^6 {. ^. i+ ~/ X
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,3 W. x$ @3 a  X
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/: S- F* V  f$ N$ f, Y
va_end(argp);  /*结束可变参数获取*/
! ~: i: o: e* `return ;
1 \( ~, W3 N% `/ ~. l" X1 p* |}, M$ [5 K4 ]6 n+ W( T% V

  H, X: z; {: t: }" j+ W0 D2 e4)可变参数的使用需要注意的问题
9 h3 C) I' M8 Y6 E* \3 n7 P* J, W( a
1.每个函数的可变参数至多有一个.; o" G# F, U. V( a; `0 X7 L
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.: _" ~4 j& M+ J' _
3.可变参数的个数不确定,完全由程序约定.
; `0 S- d6 o2 ]; v4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.3 h0 e. }. n( x& o1 G; C
而printf()中不是实现了识别参数吗?那是因为函数
/ G( V$ V: d1 ~4 c$ E% Dprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
' D  E( j% t* j) c% ~* `的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
; W7 U7 Z  r1 R3 q9 q& D5 h' k2 ?过在自己的程序里作判断来实现的.
3 F# U- T4 Y, _2 a; Q4 b$ u5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

返回列表
【捌玖网络】已经运行: