|
  
- UID
- 133
- 帖子
- 51
- 精华
- 1
- 积分
- 186
- 金币
- 55
- 威望
- 2
- 贡献
- 0

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.7 `; N9 {7 n1 _; w' _ o. o
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔., x4 V1 n3 G1 o2 C' f1 M
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
. G9 I, A3 h# N0 \$ m定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有" Y; q* ^; O- W/ f& x
实际的名称与之相对应.
g! Q$ |/ F- p6 \1 x由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
* W0 H5 w2 @+ T# C! r然而,更多地自由,同样也加大操作上的难度.4 W9 o+ Q" z' i3 ]
以下就对可变参数的几个方面作一定的介绍.& \4 _0 ]+ y# K. m1 t
1 i( H3 J; K: i. z. W7 a" I1)可变参数的存储形式.
% ]; [3 \1 |3 i; W+ G o p% ^
2 h! t" \* B C' v' I' v" A- R大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
6 R+ d6 K7 q. s, C- {存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
3 J7 }% M/ V* r在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
5 K y1 B4 l; R这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.- X5 `& J) u. w5 E, V
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
, G( e* _" `- {4 m2 E栈区:: F/ y( E! H% _
3 @7 s& w9 p& I7 X
|栈顶 低地址
8 P/ K# N+ Q0 R& u3 I6 Q0 y# M
2 p: T, |, h" [( |* V. `|第一个固定参数var18 c, a8 h, m! g9 L: ^
|可变参数前的第一个固定参数var2
' X! G: S* r i3 n8 A! d|可变参数的第一个参数% r6 E6 Z. k8 g3 o
|...
: R1 i# X+ t, ^ Y|可变参数的最后一个参数% C8 R. J2 h. X6 H& _1 g2 |
|函数的倒数第二个固定参数var3$ K/ E, L, I$ r$ @6 u
|函数的最后一个固定参数var4
/ g r" _1 @5 D) I n) m|...
% g6 i/ ?& Z! _' u; B|函数的返回地址
" l# q* q( h2 C5 S- J|...
6 ^$ a5 f t; i( L) I* h% \4 ~|栈底 高地址
% t$ [/ c8 S Y# s
4 b# |9 Q/ p% ?; C8 H% }: L$ r2)使用可变参数所用到头文件和相关宏说明
7 w7 F j' h" }4 Q, A- T( L2 L, s8 R1 J1 J( P8 w" a9 m
在此,以TC2.0编译器为参考对象来说明.
Y7 s C3 A6 v可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
" R3 d6 J; m% ]! }$ P7 Y此文件为:$ Z/ d* N2 A9 x7 h, j' y
/* stdarg.h5 a1 z% K$ W& F4 |6 }' O
' n! y+ A) j! @Definitions for ACCESSing parameters in functions that accept
1 H+ x1 R2 y4 N* J. v; x/ ga variable number of arguments.
5 U! X' m4 N: J
& c2 U7 @1 w+ [3 M2 _Copyright (c) Borland International 1987,19886 J. H6 Z- k# S3 o) J! ?
All Rights Reserved.
' [/ |' x8 b$ j$ H% D7 M*/9 k% w' ?! l" o
#if __STDC__* x) X/ n4 |1 }5 t1 G
#define _Cdecl
( _5 M4 w3 C0 i6 a$ T: D#else; |$ d: D: ~- T/ V8 O$ B
#define _Cdecl cdecl
% }7 h, X1 M. ?) m! B# [6 W#endif
" B* y, k3 a3 K' a6 y0 a0 G+ ?6 U. y A/ ?( z
#if !defined(__STDARG)
! \7 `- K0 a1 g) o7 r, m3 V#define __STDARG
; a. B% Z Q! _4 z
- \' U8 i& }) ]6 y" X, ytypedef void *va_list;
6 ?# ]4 B5 |$ ]
. ?6 W, W4 T( d1 h+ |; ~% Y% g#define va_start(ap, parmN) (ap = ...) ]$ d2 q2 `# ~ ^6 v R
#define va_arg(ap, type) (*((type *)(ap))++); U0 `* V8 v$ D y# q3 N7 g& Y
#define va_end(ap)
z/ J6 N( ^0 q; A#define _va_ptr (...)1 V) _$ q6 w6 F0 W8 O/ p
#endif
4 V- \" K. Z) o% l7 K5 G9 Q
: M1 G& l; ? Y$ L+ |以上为"STDARG.H"的内容.! x" l1 e) \0 Z: B& ?4 o
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
7 ]* N4 M( v# p% I% X- J8 \: Lva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,8 _; D4 A( h& P2 B
parmN为可变参数的前面一个固定参数.* v: M$ r$ X$ ?' \
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.9 D, B: x/ u" m; [+ Q9 a; }
va_end(ap) 结束可变参数获取.
$ F" q( U/ m q8 V# W$ u! u0 \* f
2 x- }& p9 I' ^( e3)可变参数的使用实例* q, @0 k. v6 P- ]9 Q: V% {
# A$ W: t' M: p" k! G B1 A' I实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
* F; Y) |3 R" `$ p
+ g/ p$ A+ e. p# m# }7 T#include<stdio.h>) p1 |8 j7 d' h4 x; f
#include<conio.h>
& W; W% |+ J3 ?' h4 [#include<stdarg.h>. O5 ], r& o: {9 M4 I0 F
void tVarArg(int num,...);/*num为可变参数的个数*/
$ g1 k) I+ {8 x! Wint main(void)
. V% B* G X" ? f% q( l; U G3 k{! @' Q3 B4 _, K! Q5 J* P
clrscr();) T0 @" H; d1 O4 `
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");2 S: U- H7 A6 z: M. b M ^
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");) O+ a1 ~. _3 D- O% ?
getch();4 G9 ]; _5 v0 i! Z" N; d7 @
return 0;% A1 h8 _; G. T6 A
}% R0 u; E4 W3 d4 @, K4 _
void tVarArg(int num,...)
/ t6 [5 h: |. ~. k }+ t& U{
- A: B) N$ i0 ava_list argp; /*定义一个指向可变参数的变量*/! l" ?' \9 X& a' h
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/" _. a1 H1 m0 }
while(--num>=0)
8 J: J( |+ E8 H7 Z( m. {* F( E printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,* U E8 X5 f, l- A/ o
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
2 w! K! i7 E7 |5 W8 _va_end(argp); /*结束可变参数获取*/) Y" X1 z( [# r
return ;
2 C/ T6 Z0 H4 r! j}
1 }5 [( }- C4 o; \
1 w1 F; \% {4 F6 z+ U4)可变参数的使用需要注意的问题
. c# s/ ]* s: b+ H6 x# y
$ O/ y3 y" w( w% _9 k$ u. R1.每个函数的可变参数至多有一个.
$ o d8 M9 t4 y1 @ {% U% p2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.; K. ^9 y& A+ _: B$ @, O' Q
3.可变参数的个数不确定,完全由程序约定.
- m$ X) U" O) J7 }/ U% `4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.; ?1 [- I3 g E& ^1 U7 P* s
而printf()中不是实现了识别参数吗?那是因为函数 ( m: j9 j# o) \; l" [
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg $ P1 D) \, ~8 g3 r! a0 D( W3 a
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
1 G: S3 _0 F6 g1 q过在自己的程序里作判断来实现的.
6 s- a' s+ d4 n& S; c' a5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|