返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.7 p" F# J* t: m8 A
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
6 ?. ^1 }, x+ N  s可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
8 o4 J1 H! d- b5 t! Q- i定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
+ P- M0 N3 f5 v' V- c8 J实际的名称与之相对应.% r. D) W2 c, z
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.- {3 }. d0 S" b9 b9 Q% m7 u
然而,更多地自由,同样也加大操作上的难度.
: q' Y# U7 m" o2 b# G5 d3 _以下就对可变参数的几个方面作一定的介绍.5 ~# h5 y/ y$ K

/ ~- w+ B, w+ I: K3 b' X# p8 o/ z1)可变参数的存储形式.! P' M- f/ C, j( y2 B6 F6 ]1 ?

$ F! J' |  Q7 W2 A大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
3 B9 \1 z( |; k, y存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
& N3 v7 ~! d. A5 y  }在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
( d$ _9 R% G3 y& I2 a% j% W这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
, D  w" U: I6 b+ @' w因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
. a1 ]9 r' D. ?# e1 V' k栈区:/ e' k4 t% s; L/ _- Z

, d" w6 T4 \1 v- [" C|栈顶             低地址; H% e; K- S1 K0 }! L
/ p9 Z( _/ ~/ k- U& X( e9 n
|第一个固定参数var1
7 Y# q' O! P$ E  W|可变参数前的第一个固定参数var22 B9 o" e5 E( T% r* m
|可变参数的第一个参数
7 F) }0 e  N! M7 S$ r|...  a6 [8 N! t9 L
|可变参数的最后一个参数
1 U9 d8 G- g. d|函数的倒数第二个固定参数var3: o; Q/ r# ~; A8 G/ o$ {; ]
|函数的最后一个固定参数var42 b; [  I2 B# x4 z1 u+ _  b) V
|...
7 Y/ q  X, z3 z6 {  I8 V- e) h|函数的返回地址% x8 G; Z2 P& e  L) G0 Z' i' U
|...
: o  ~8 N3 v' b* P( v9 k" o|栈底    高地址+ `" T- e  Q9 l/ _3 T. e4 C8 {
8 C1 t3 R# d# q9 ?8 h0 ^- O& N% E
2)使用可变参数所用到头文件和相关宏说明0 {7 A& N  _) T( s4 k$ B
" o# ^( ]2 S$ A1 [
在此,以TC2.0编译器为参考对象来说明.
& {+ L/ P- z  ^2 d/ {  ]1 w1 @6 j可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
8 S" A/ A& a5 e5 W9 W$ Q- _7 Q3 i/ K$ _此文件为:+ X( g* ~# d/ ?$ h$ U: H8 i4 }
/* stdarg.h
' Y; S0 Z1 K8 o9 C
# I3 Y: U/ _8 O+ v1 ^Definitions for ACCESSing parameters in functions that accept" ~1 d. t- [% l
a variable number of arguments.1 K, D* [0 z' q0 D' B! F
7 q% u5 O8 ?" C& K, E
Copyright (c) Borland International 1987,19884 e2 ?% ]+ f5 `/ S6 O, L
All Rights Reserved.
" J9 ?8 F7 c7 r$ Y1 p( y*/" ]- M# u: ]4 a; Q6 F7 K/ n7 l
#if __STDC__3 }2 o8 D/ i* J. J: ^) G; K$ Y
#define _Cdecl" I2 A4 L5 R( F- }1 {+ |
#else
8 |) t. ]; W3 i( B7 m  O) S4 A#define _Cdecl cdecl! o; t4 @' e! G+ T
#endif0 _6 G* I4 a# Z2 T% _5 N. j" ]$ s4 T

. f" p8 b! M: a#if !defined(__STDARG)
% J5 H/ W8 X5 v( K! D* h#define __STDARG! }4 i6 k4 B6 x. j5 w0 Z

" x  H# Q- a8 A/ O4 \+ a8 d. Stypedef void *va_list;9 [' ~3 w5 D" c: k6 X8 H0 `) {6 x

1 ~: [/ p3 E$ Q' {- T#define va_start(ap, parmN) (ap = ...)
  U, {7 q9 r: q, c6 c' ^#define va_arg(ap, type) (*((type *)(ap))++)5 k" d- {. g2 R% |# `2 Z
#define va_end(ap)
. ?! d! k! n6 J7 `: A2 l#define _va_ptr   (...)( M: R" {* {% j4 u0 A5 Z
#endif
( E" T: H  c$ l
" W' V  p  `) N8 ^以上为"STDARG.H"的内容.
6 ~/ f; p' z) L2 I. j- B该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;% y, m5 i% O9 @/ T* k
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
. \+ I  z1 O+ H3 GparmN为可变参数的前面一个固定参数., V; C1 n, |3 l+ c/ J
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
8 N1 J" N2 U0 M1 q4 p! B* Q! u5 K4 t3 Bva_end(ap) 结束可变参数获取.
" F  t* X  x( \% y3 |6 O) {- V& V& y
3)可变参数的使用实例
, X" d8 c+ T5 P; S7 d/ w- u! |! Q1 ^. m1 A
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
! D4 ^  o" J: f+ l+ f. q6 c" I  \% O6 Z' x' v1 A, u, @1 ?  g
#include<stdio.h>7 u/ ^. o4 O7 M: S: H
#include<conio.h>  C, C* m' z  Z% j
#include<stdarg.h>
' u5 W1 r! a, g  ~void tVarArg(int num,...);/*num为可变参数的个数*/
1 k. P3 [3 D3 }; X: i7 Wint main(void)# w9 \8 L# _: {  `( f
{6 l. N1 x! j6 u3 Q, H- P
clrscr();
( f* [' B1 U% j& B4 ItVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
4 ~. {. s. g1 Q* ]: A7 LtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");( k- p( |) ~4 ^( P, i; L
getch();
- m. w9 s0 s9 c) x4 greturn 0;- G; H. A' i% j- b# a2 H0 W
}
8 V, U7 ~& T% r' Y  avoid tVarArg(int num,...)+ P# X# X% G4 o2 s( F- M) @
{
0 R# \0 u1 ~  V. J4 o' [va_list argp;  /*定义一个指向可变参数的变量*/$ P( F' u9 Q% }6 q* e- ~$ w
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
$ o8 `/ {, }) o8 H6 C' }4 Cwhile(--num>=0)
! A% s/ l) H+ i- T0 A  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
& D- J6 ?$ B0 T2 h9 A9 P$ Z    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
7 o; V, z' I% ?, a9 E  J, s5 Iva_end(argp);  /*结束可变参数获取*/
% L5 ~0 }8 F; i' ~return ;: [$ w1 @: ^" \- K8 V
}6 V2 [! T+ F, Y: j) b! W2 @
0 o$ K) s+ X% R6 I# |
4)可变参数的使用需要注意的问题$ K% X' J7 T# h/ R4 ?+ C
3 Q8 N' Z* B; T' ^8 B
1.每个函数的可变参数至多有一个.
! f% q! E$ v5 q" n# h: b; \# ~: y8 U2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.0 a3 c4 [' \6 F8 b# W+ T0 Q. A9 E/ U
3.可变参数的个数不确定,完全由程序约定.4 j/ ]0 d) S" C7 x- y# O9 H3 R% h
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.# K3 Q4 ]0 f1 _0 \0 a) x0 m
而printf()中不是实现了识别参数吗?那是因为函数
! H4 s$ N( F7 V0 [printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
6 ^2 @5 m5 j' w的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 5 o& J# \3 d; F& C* w$ `
过在自己的程序里作判断来实现的. * c. }/ p; ~- C( H8 ]
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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