返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.
* i) N: R; s/ j0 @2 P" b1 Q它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
1 {3 Y/ P5 O$ q) |, c' a可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不; Z5 P+ k8 V9 Q7 \3 Z0 u
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有# a* z$ P& Z$ H  p2 p
实际的名称与之相对应.6 W- c% P- s! }: T: y% B) B
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
1 O0 X4 S5 ^  A) G然而,更多地自由,同样也加大操作上的难度.
1 ^; Y# q( F& O& g. _0 R# v以下就对可变参数的几个方面作一定的介绍.
& t! o2 @/ S( a0 s: g
" Y- j% I* `6 a* D5 w$ ?1)可变参数的存储形式.
, A( c! E" T! F/ G- H
) f( I9 P/ O3 \" s+ p大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
7 [  I9 {/ A9 f9 N, |/ Q9 i9 [7 @存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
  P4 p9 N. X1 R& K在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,9 K! A/ W& N1 C& y! D! J
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.$ e8 S- t* Z: X7 @
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
6 j1 [  Z9 H0 ?5 z8 b$ s3 V栈区:
& @% q; ?8 P3 H+ n( p
( j2 i4 [: d; `4 P1 Z|栈顶             低地址
/ D" I$ X/ A( [3 f* X! u; X! k1 t/ e5 Q
|第一个固定参数var1
" w* P& i9 l& o1 V) z|可变参数前的第一个固定参数var2, W" z8 U1 h1 Q$ p  U7 G
|可变参数的第一个参数+ B1 N5 ^$ ~* q: T7 \9 g
|.... L% i) n" Y1 r4 [) T
|可变参数的最后一个参数
" v' @6 S, A, u4 E* s|函数的倒数第二个固定参数var31 ]+ [1 I0 {$ j( L1 {
|函数的最后一个固定参数var4. ^0 \4 H2 k4 k
|...3 F8 ?( z$ U4 p% f. P! o! I8 S: E
|函数的返回地址3 `; U4 c: G  P. n7 ]3 Z1 J
|...
9 u( J2 E7 r2 s( Y  h|栈底    高地址$ P5 j, `7 L. O& C7 _( x4 R, @7 _
6 ^" N9 F1 g" t* v
2)使用可变参数所用到头文件和相关宏说明
3 o+ R; b; }2 T: B( W
3 K- T5 R/ ]9 ^' d在此,以TC2.0编译器为参考对象来说明." \6 k* m& v9 f/ V- `7 \
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.0 h+ U! f  D7 S) {/ u
此文件为:1 G6 n' k9 a( {
/* stdarg.h' I; _, N3 O: f" w$ R5 |" P% x2 W
' O$ K8 h. c+ A) u' W
Definitions for ACCESSing parameters in functions that accept/ i: a$ \8 }/ H7 Q6 U
a variable number of arguments.) M" k5 I0 o% ?: A
! s, G8 t  V- e2 L+ M
Copyright (c) Borland International 1987,1988: ]1 u/ U/ O' w9 O! D8 J. [
All Rights Reserved.
- A4 _, z: t# a+ h*/
2 o6 [% p( _1 w4 A#if __STDC__
1 W0 [6 L9 J" ?# o. T; \! U#define _Cdecl
. o: y0 R9 ?' Q+ H0 A#else* i% E! Z$ O# K9 E
#define _Cdecl cdecl
& ~" r' r9 y3 U2 I#endif# F  {" k! P* u
+ t3 {' G! {$ D2 T) v
#if !defined(__STDARG)2 i% H" m$ U6 d. F) B
#define __STDARG$ M& k! Y' l9 o) r  l# d% j5 p3 C) C
% `9 _" D4 `# G+ S3 H+ u) Q4 a
typedef void *va_list;
# Z5 E1 ?9 C  M, B$ s: A0 P
) O5 ]# J% S# E' e0 y- z0 U#define va_start(ap, parmN) (ap = ...)
3 |+ J8 W# Q) T5 i) o7 [#define va_arg(ap, type) (*((type *)(ap))++)& l; n2 ?# J# j, V7 t' x3 r
#define va_end(ap)
2 q- ~9 o, I+ B#define _va_ptr   (...)
* }) c- t1 M9 b/ F! _#endif0 Y9 m3 K3 S7 d) _
- e, c8 x0 q  G0 P% K$ s
以上为"STDARG.H"的内容.
! F7 i1 Z. n0 {- O该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
9 m. U, j& Q1 h/ L3 ~# Kva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
& x- k+ @/ ?1 H& r; t- z! jparmN为可变参数的前面一个固定参数.
  `$ R0 Z9 F7 C3 D# B( Tva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
7 S+ H4 C1 ?1 c) O$ C- x- ova_end(ap) 结束可变参数获取.) C! _5 j8 _  K/ b9 y

9 b" v  P$ i5 A7 R# @8 O+ M* }8 v7 ]3)可变参数的使用实例
5 e1 z$ E) ~/ G% I. c6 x( Y% _
, A8 e+ c% B+ x0 h实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
7 k- `  P8 B3 D1 @/ q8 `
1 o( q/ z$ f. W8 C- M6 D, t: O' A#include<stdio.h>; B) G* _7 m  y$ b
#include<conio.h>! v7 D! j% o! ], _
#include<stdarg.h>! J1 p" ?1 c5 y: d% ^; A
void tVarArg(int num,...);/*num为可变参数的个数*/! K; ]' v* F. P9 p* ~& e
int main(void)
# V# ]8 E0 j3 p4 d9 U{; s, P) s6 ?* Y
clrscr();
: u1 l: n% x4 M% r! t* LtVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
. ^# }9 H7 N& v# W; @. G+ m: A5 ztVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");. r! R) F9 ^0 E4 t
getch();
: f& q$ M8 l, ~5 x8 r- Qreturn 0;* P' f% M7 C  m3 ^" r9 j
}+ N- q9 v1 U6 O$ r* q" e& d8 {
void tVarArg(int num,...)
' M1 _$ q3 N& g  |$ ^6 S! e. S% C{7 Z0 A. I6 J5 d( G
va_list argp;  /*定义一个指向可变参数的变量*/" W7 X6 t$ x% [7 E1 c/ h
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/) k# M& c9 ~, a7 k
while(--num>=0)
( ]1 _2 K1 H' o; _: J$ B  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,3 v! [9 t# u# _. f( d
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
" y! ]! }; v( W3 f* qva_end(argp);  /*结束可变参数获取*/0 |2 B  l; D6 z! `0 Q- E
return ;( a/ k7 B8 Z& J/ ^3 D) @0 m
}3 y& }& [' v; ~3 b6 |
+ x" @! t3 r& m* w. H0 _
4)可变参数的使用需要注意的问题
- ?5 W6 p; c- v
  @1 r  b1 n- f, x; ]1.每个函数的可变参数至多有一个.
* {* `* F1 ~+ P5 L: P' ^0 u) [6 \2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
/ ?9 L3 V0 H# J/ O3 [8 r3 z8 `3.可变参数的个数不确定,完全由程序约定.+ Z3 f/ g! o4 L: m4 [# X3 u
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.+ w- e  v) v0 o
而printf()中不是实现了识别参数吗?那是因为函数
  P6 P( o/ J/ {9 ~% x5 V( M* C# zprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
2 [- O* G1 l" R) N! v- |4 k: j的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
" ?: J8 ~+ \9 a- I: F* ]# a  ^" ?过在自己的程序里作判断来实现的.   U3 l! s8 y: N* @5 Z
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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