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

函数的可变参数详谈

可变参数的英文表示为:variable argument.
! p6 Q4 D! v" a7 r$ E它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
, r; X! a1 f" Z$ t  M/ @可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不" F0 T6 b* U: s5 W5 u1 V6 j
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
: O' ?6 C3 p8 Y) n' ?0 m2 U5 C1 C实际的名称与之相对应.
. a" }- o) q" C  v; z由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
8 q; J3 ]( T( s# ^9 y: U2 n然而,更多地自由,同样也加大操作上的难度.6 O7 P! @! l! \3 G: |
以下就对可变参数的几个方面作一定的介绍.& Y5 k( i7 D  I6 B# t

6 Q8 l6 @/ [8 x* I. D1)可变参数的存储形式." l5 f8 @* p/ G+ W* m

$ w& p! Z5 K1 g7 R+ ]5 T大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
. z- B0 @1 h3 g/ M& ?  s5 ~! h( U存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
7 i% B( c3 X3 V$ q& n* p! Y: x在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,9 m& F- x7 `& O0 G, M/ J; c
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.7 G+ q8 F% {, V( a! d0 \
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):% j  L# A5 {* l6 p  c
栈区:' e, `) @$ x9 S3 g- ~
& @  C  d2 {- H1 S) V
|栈顶             低地址* j! ]! {# L! F8 N. ]; C" o

9 l* |8 E' L! y& ~' l9 s) \) ^|第一个固定参数var1
3 w. Q& ]3 c2 z9 v2 d+ B|可变参数前的第一个固定参数var2
: x: N1 K4 L$ P- X3 }# A$ O3 \|可变参数的第一个参数
8 T, o! y7 r4 D8 h" H  J- n& C8 I( ~|...  o# d, ^& Q- x
|可变参数的最后一个参数
. k. Q0 a) r1 M" j! k  X2 c' a) z' g2 q|函数的倒数第二个固定参数var3+ S# L+ p* j$ e& X
|函数的最后一个固定参数var4
# h6 X4 ?8 K: e# o% X|...' t0 v3 ~  I% l. K) i& s' o
|函数的返回地址: ~" n  M; x1 t4 |" N5 p/ i! q) g
|...
! Q) V( T% ^$ k/ C8 t) `|栈底    高地址; H2 W9 ^6 d/ E* y1 h! \* H
; ^) e+ F" d6 v
2)使用可变参数所用到头文件和相关宏说明$ }; ]2 `+ m0 v4 o
* Y- Y1 t; N8 b
在此,以TC2.0编译器为参考对象来说明.
& f. `9 D# }" X1 @可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.+ @7 c) @( I& s- l: j6 Q
此文件为:
+ M# V( @/ `1 a$ G, M1 u- r6 u" |3 |/* stdarg.h
2 d, i# j& ~* y. e
7 D! i2 Z" y! w# KDefinitions for ACCESSing parameters in functions that accept
! b! R3 k% n- }- e8 Ua variable number of arguments.
5 \9 W1 C, _7 V# D! M8 P3 Q
  R# T/ K. ]) q- R2 z5 G7 WCopyright (c) Borland International 1987,1988
/ _4 k# {2 x% P4 o" O0 t) aAll Rights Reserved.
( ?, W0 z3 Y- O7 g& V7 g& S! d*/
8 h* e% Y& W) o  }/ g#if __STDC__  w- r3 @& m' ^
#define _Cdecl
/ A! h: K; f0 b#else
' q3 t. Q, }4 \! j/ z, ?#define _Cdecl cdecl
( v0 n# G" R% _9 Q: j#endif
! W* w1 E0 ~/ G
, F; v9 z: e# s. t& j8 ^  D#if !defined(__STDARG)
  V1 P7 U7 |7 G- ?" |4 [2 s$ H#define __STDARG5 o  j6 X* X4 B5 z

( ?# {5 W; q' m3 utypedef void *va_list;- V+ A( n3 _! \+ C' [* ]% w0 s8 G
3 u# O  j( A0 J; \) B6 h  [
#define va_start(ap, parmN) (ap = ...); E) e/ o2 ]" m% v7 _
#define va_arg(ap, type) (*((type *)(ap))++)
: l6 o. I8 ~$ u. t) M#define va_end(ap)/ e& b8 a0 z) @- H  X
#define _va_ptr   (...)
! a; q- K  E. r) d" M#endif
* F" w3 f( H# `4 c9 P7 w, {) n% I3 ?. ?( U
以上为"STDARG.H"的内容.
6 V+ q: Y' @  v1 A3 V该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;$ o! U, b) b+ @; _" g
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
% b- }9 i6 A# _2 h/ AparmN为可变参数的前面一个固定参数.
  c. h# ~4 P, \4 ava_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.) T7 b7 O' u9 O8 M0 q
va_end(ap) 结束可变参数获取.8 o8 n3 _4 `$ O5 P) Y+ {. a

1 G: @* [0 }( T' j2 R' l8 V3)可变参数的使用实例) ^2 \( A, K' R7 {! S# g" [
) ]1 C$ h3 q' K! E) D+ J
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
& [$ K& n" C, ~& C- P: A
$ a% B$ j, @+ t: v" |8 W; U#include<stdio.h>
4 G- l. D4 ^3 Z( W* m#include<conio.h>
. T7 _. K9 A4 Q#include<stdarg.h>' m+ A5 Z0 M5 G! c# h  O
void tVarArg(int num,...);/*num为可变参数的个数*/4 t7 U8 ?3 L$ ^  o6 a) r! g
int main(void)
1 o" L1 |+ A  p2 U{
4 s, l7 w& @8 ~, f0 `, z9 ~clrscr();
. p0 ?; w1 \6 n2 ?; J0 A- LtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
4 \1 g& t! Z. ?  D( R8 `9 I# qtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");, i0 c( V7 T3 O
getch();
! e3 e) A* @; ~2 `/ E0 V0 \1 |return 0;
$ ^1 Q. c. k9 P, P/ S' \}/ o( p7 ]9 n1 K9 @" [
void tVarArg(int num,...)
* ~' ]& E6 |* Y) D0 v! l& j5 a{
, N+ z! A7 K$ n8 ^va_list argp;  /*定义一个指向可变参数的变量*/8 G' @. R! A8 G3 i
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/3 o4 I0 m0 H1 }
while(--num>=0)# s0 k- X' q/ x( u( m
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,  G, h0 @0 z# Q! C6 J9 G7 i" b
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
5 R) H+ O: N$ b# \) kva_end(argp);  /*结束可变参数获取*/; {9 x: K% H/ w6 Y
return ;
# |. t! z) C3 Y1 G}* M8 m6 S' J7 r7 f
% ^# V' h3 b* G5 M0 @; q: y
4)可变参数的使用需要注意的问题
; D, O0 U/ `0 W8 q. H( M/ j: ^1 V! j' n; `6 r* n2 N
1.每个函数的可变参数至多有一个.; o0 l6 p4 V/ c. n$ \
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.# S2 X: a. U' @, v# v: ?
3.可变参数的个数不确定,完全由程序约定.
7 _4 ~* X/ P: c  C4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
: D' `% m8 f0 d$ C而printf()中不是实现了识别参数吗?那是因为函数 ) C& E4 J$ s7 o5 z
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg + {5 R8 e. z8 r5 u8 j
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
3 f. p" }$ V; G8 @过在自己的程序里作判断来实现的.
+ @4 \" f! M# x* C& R5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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