获得本站免费赞助空间请点这里
返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.# R6 h( }. }  M/ \/ D- v
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.$ T; E0 H; R1 y+ @, X
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
  G* F- v0 F( J. _定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有# g) r% ~7 Z) x' W1 y/ k
实际的名称与之相对应.
7 [2 c( |9 a0 i4 ]7 H; A' O由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.) k# p! L' d0 N% t! E9 c- ]4 ?& Y
然而,更多地自由,同样也加大操作上的难度.
0 A- b6 t' H& v0 q. x2 ~* G以下就对可变参数的几个方面作一定的介绍.
) D) l1 z$ x+ f) d# q2 O! Y; x5 k) d" [
1)可变参数的存储形式.. ^- r% J: Y/ f+ B( N" V

9 W) Y+ }/ I- o9 A, X大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,- a; U/ s5 T5 h9 [2 x. ]. _5 k) [
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
( |6 O0 X4 p/ R3 {, [; y4 z) Q: v在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
  R4 E3 t" m# s1 ~. e1 k这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.9 i! f* ?$ C9 J+ ^+ A. J. ?" f
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
9 {+ k2 T3 F+ U+ I# v1 @7 e栈区:
) Q8 s% r% K9 r9 l+ Z0 O! }$ M9 q# w) x# E% ~+ o2 K) `
|栈顶             低地址
! z- m% h- |1 d# \! I
$ K% y8 `  Z. P! n& B|第一个固定参数var1, Z# S  V; P% d( V9 t
|可变参数前的第一个固定参数var2
6 q+ x4 `2 h; ~$ C3 F|可变参数的第一个参数
# m4 _# K% r# n/ }) t|...3 \$ d7 L& n0 l5 h8 Z; v
|可变参数的最后一个参数$ P9 n3 k& [( E  \
|函数的倒数第二个固定参数var3! c2 }7 U( A% J$ B) K( q9 _2 H: T
|函数的最后一个固定参数var45 y% d% u$ m% I
|...' _4 C  m. K9 e. l( V1 O: V
|函数的返回地址
7 P/ {3 y$ z1 N# c. e, ^|...
; A: D% c, x" F* z9 W" U; \6 F|栈底    高地址
- J9 s0 I- S" q8 r( |; K8 A$ j. G5 V' d) r* A
2)使用可变参数所用到头文件和相关宏说明, K. G0 ]$ k6 s& G: I6 A

4 \( g8 E/ B) [: t5 \在此,以TC2.0编译器为参考对象来说明.  X3 _; L" L* c& b2 g2 `' g
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
0 e& O: n5 g% X$ L, c5 t此文件为:
) S  a8 o" R0 k/* stdarg.h; o3 k9 l) _3 c
7 P- q- D% f5 c0 f- x' q# H
Definitions for ACCESSing parameters in functions that accept
- s4 a4 |  `% Sa variable number of arguments.) W+ m  q, ]+ y5 h1 A8 T4 N) w

9 Y! D. H4 s: OCopyright (c) Borland International 1987,1988$ X3 [, |7 T! z1 e0 |' P1 \7 T) P+ R
All Rights Reserved.
) r8 \" Y, S! F0 U*/. `6 q+ P, R/ a1 _! t" y! g
#if __STDC__
: k# Z) C9 s& ^6 R. f9 {4 L7 A#define _Cdecl- _! D+ {5 [4 K* }+ o) x! v  S
#else6 r4 Z7 `; A& b+ w0 l6 ]* R
#define _Cdecl cdecl
  _; V8 l' o- I# m0 W1 l) ~#endif. u% O, i  d" k' F: n5 n* s
+ y- N4 I3 L. O6 Q! Y1 z7 r* |
#if !defined(__STDARG)
" F! q( c* V$ o. |& N) F! c#define __STDARG( [; U2 j) m3 L* L

8 w& j4 @: p5 B9 n7 z; atypedef void *va_list;* X4 u" s# f* e' }1 q  ?; [
8 d1 A) J5 ]$ c" }& M+ e2 @
#define va_start(ap, parmN) (ap = ...)* r* G" W$ m" G# R7 A
#define va_arg(ap, type) (*((type *)(ap))++)8 A2 w) Q6 I! u5 m% I5 ^) Z8 e$ w
#define va_end(ap)
1 v5 ~! I4 j) t#define _va_ptr   (...)
4 Z3 b0 S; B2 m) o! ?% c( a, _#endif4 L* f5 f8 R( V2 g6 a1 Z' ?
. i' j6 f& I3 F1 H% F2 `* a$ A9 i
以上为"STDARG.H"的内容.
7 z6 n0 ]9 d1 b- n2 d2 I该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;& B0 ]5 P' O6 w+ d9 y
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,6 h4 q' s8 ^2 }
parmN为可变参数的前面一个固定参数.4 U! n) }# \8 r$ q; ~' [# K
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.% |0 ?4 M+ }) _# L- S
va_end(ap) 结束可变参数获取.
% o# [4 b: b2 s# U# U1 v0 k# G1 t( y; r3 L& {1 g& Y) k2 S
3)可变参数的使用实例* E- o- R6 ^4 p" `2 W: q2 u, u
7 J" @, z5 {2 {. t" @
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
7 X' `/ T4 [( B+ O& h
  D; S! C: x+ ?$ \: G#include<stdio.h>
( @3 G1 Y+ B" u6 s1 D3 L5 G, G#include<conio.h>
% r( E# V% m" y- a) h#include<stdarg.h>7 @, U+ J  y3 |0 u" @
void tVarArg(int num,...);/*num为可变参数的个数*/2 l, H2 {$ z( u* U7 H* ^* k
int main(void)
, f$ `" I' o- X" T8 _6 K{
6 G& s7 s) W; _) u0 ^) x# g' {6 Nclrscr();% p8 J5 k" c* E0 u7 d  x' ?
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
. S1 O3 b4 I+ ^& l4 G  L1 mtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");8 F! m  ]7 H8 F% B" m2 I, M
getch();0 n2 a% [5 J  Y9 `
return 0;
1 c7 [$ K1 R+ A}" k$ h5 h/ ?' x9 ~' J
void tVarArg(int num,...)
( a, v6 a  o0 a" f5 P/ V{7 H0 A8 Q0 [) C# W
va_list argp;  /*定义一个指向可变参数的变量*/. K7 m. X& ?* c/ u0 k2 J; M5 Z' I
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
: `# e( E5 Y* q" w* S1 r2 q1 b* swhile(--num>=0)
$ M3 J3 V* h) g$ X4 O9 n; H& A1 V# U  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
. D" X( d% D6 a    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
: t+ Q7 \- B% {$ m, U% x5 v5 V2 ova_end(argp);  /*结束可变参数获取*/: i) K; c8 u* Y: I7 D8 h0 w" w
return ;7 I  z3 p2 |1 v: u
}3 `/ O( e1 d4 J0 h$ T

% {! B( Y9 A4 g4)可变参数的使用需要注意的问题
" I( M3 {6 {# W; U- d
. j* w: A, A! `8 n% Z0 ?1.每个函数的可变参数至多有一个." ]) E! r3 z  n3 x# G
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
# `# d5 [3 L. H" n* O  s5 f3.可变参数的个数不确定,完全由程序约定.
1 b1 R/ k; y+ f* d4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.9 {6 n) v' T4 s. S" j5 a  @
而printf()中不是实现了识别参数吗?那是因为函数
% h- \8 W# p9 }; x. w( A# Hprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
# M5 {1 _' j! S7 O, f$ l的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 , p. Q0 l# A8 C7 c% d( A( J4 B
过在自己的程序里作判断来实现的.
  t% d, K  W  R# R. Y+ [5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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