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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
3 b. r- B" I4 G% s! j1 v' m它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
R0 x8 s- @: C% e4 S5 [+ D/ @可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
3 i1 E8 p$ y& v8 Y- R8 f* k定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有, e" C1 p* K7 r$ p( T& |7 @4 o' q
实际的名称与之相对应.
4 H I) a$ I1 N* A; g7 G/ I由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.1 g0 H) Q* L, p7 W1 M* e) A
然而,更多地自由,同样也加大操作上的难度., U" G# h/ Z, p0 g. @- Z
以下就对可变参数的几个方面作一定的介绍. v4 E, D3 I0 o7 x
# L6 r9 y4 j; ^$ s3 S9 c3 c1)可变参数的存储形式. j) Q1 t, {: w# X, R, {
- A* S7 ?% X' h; D" f$ R5 }
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,# Q7 |4 c0 T3 B/ H7 J7 F
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
' b0 w4 o- A r% P( Q. t在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
; x6 b* K: d% l1 P& v# d, L B; u: i这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.. U+ j* b* w; e0 k: H
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):, y$ T: U0 G0 x- ^9 t; ^0 f% x
栈区:
0 F5 q+ i1 Z$ z# w" \, ?) K! P* I7 q: w- \
|栈顶 低地址2 ^" c) o9 ~! [
( s) Y& k; i4 L
|第一个固定参数var1) z9 A2 j2 ]8 f1 c- Q4 v/ \
|可变参数前的第一个固定参数var2" @/ G' K$ }/ @9 |" ^
|可变参数的第一个参数
! j0 p3 l, N: t) f5 F7 Q2 q0 \& D5 ~|...
1 p- Y+ Z/ Q* m$ t& g# G|可变参数的最后一个参数. G$ |/ d. n3 ~5 L( r0 q
|函数的倒数第二个固定参数var3
7 t6 W/ p+ Q2 }3 A Q r|函数的最后一个固定参数var4% T; T6 M* A! l$ O
|...
+ W0 @; d3 {! ^7 _; L3 ~|函数的返回地址
) _9 s, G9 B4 d; D1 X* ?|...* X$ M: A2 q# A; M3 C% P7 d! B9 o
|栈底 高地址( D3 l& v# v7 `2 l! H
7 d) Y1 m, f) @; Z! y2)使用可变参数所用到头文件和相关宏说明+ X2 [$ x) w1 ]4 [8 j
4 K5 [) J0 i0 F4 O! f- f& ?
在此,以TC2.0编译器为参考对象来说明.5 Q0 R: O. i' {# m9 e
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
, A. I' D+ ~9 N5 ^此文件为:* i+ d, _2 Q9 w# Z7 A! b! Q
/* stdarg.h w! _, C% M* B
9 s- b3 e/ ^' x2 J6 VDefinitions for ACCESSing parameters in functions that accept
5 F- Y2 p$ e. i0 J* F7 ^* b& ]; ~a variable number of arguments.
/ N, o9 k, j+ G' M G) w0 T5 Z) m+ l4 `! c: ~" A2 J; d
Copyright (c) Borland International 1987,19880 b0 o+ W/ a M9 k; q
All Rights Reserved." Z! [# E+ c. I1 ]5 p4 t
*/3 c4 }; k$ ~ r5 Y7 o
#if __STDC__
/ G* ^/ V7 Q9 K- T0 c+ W( Z# j#define _Cdecl
: I1 P2 B' [ O B/ P#else8 |* V. Q: m$ O, ]- O" H
#define _Cdecl cdecl
$ g5 J- A) E5 E1 b#endif- c# `, K+ L6 r2 u" C2 P- o1 z3 b2 A
+ X$ D% L Q% d8 R) @# N' q4 d! u+ F
#if !defined(__STDARG)4 [. K- s5 c2 O5 m
#define __STDARG
3 w0 i* e! T; u* v+ z, b- F8 n1 X% k! ]0 L) b
typedef void *va_list;
- O8 U, `9 K4 W$ ?9 s
) [+ I( H" X& |7 B2 q3 R- `, Q#define va_start(ap, parmN) (ap = ...)
* i$ z5 k+ x4 D( }" O#define va_arg(ap, type) (*((type *)(ap))++)5 T4 y! n `8 B! g1 p$ D. B% W6 o; t
#define va_end(ap)
% L; t7 G) _# c' P) I. ~#define _va_ptr (...)
9 L' D; {+ `' M1 ?#endif
) N8 K9 F+ J5 N3 i- C$ m8 |* C- @& ?0 a
以上为"STDARG.H"的内容.$ l" {$ C6 K0 r; b6 j0 L
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;4 T# q6 s8 Q9 a- W
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,3 U$ Z$ z& [" k' Q; K, M$ F' Q
parmN为可变参数的前面一个固定参数.
( R, Z7 `7 U- N$ T h! d7 gva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
+ J% P+ [ f9 nva_end(ap) 结束可变参数获取., I% f# P) H. l" _/ U! S4 o
C- R" L* S6 d$ v
3)可变参数的使用实例2 Q" W/ C8 T) G! G; |" \
/ u' B, P/ g8 t. e实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.+ j( E! W" y6 v: k1 f9 R: e8 {7 S
3 g9 q. t* y( F. M1 ]
#include<stdio.h>
, r' f. h" {4 n. J( O; t6 D#include<conio.h>
! U) }1 {1 }3 I0 m. X+ F) E#include<stdarg.h>1 Q) T. J$ u* x1 o
void tVarArg(int num,...);/*num为可变参数的个数*/
4 m- z. a r* l* o$ J8 e3 w% [9 ]int main(void)5 O. F) b6 z8 |. N1 m
{
) F) }" B9 N D2 ~( e2 V4 iclrscr();% b/ [$ Q, Z' w
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
5 n1 e6 j( {: e8 \# wtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");/ A; p. `# ~* \# v
getch();
% h" I- h' y' j9 i* @" Creturn 0;3 b% q5 @; Y9 i$ \- v0 H, u: B
}4 p0 U, X7 P: i- U
void tVarArg(int num,...)
A; M7 h$ b7 J3 e{
9 e( e# p* D( d* A: jva_list argp; /*定义一个指向可变参数的变量*/
7 I9 F' Q+ a8 i8 y( u' S# Dva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/6 G( ^* Z% ?4 r, A+ X3 ~: z, R
while(--num>=0)4 k' h$ S+ _% G/ T4 F9 I+ p3 ~
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,- p4 K% s `2 I2 T x- F0 j, b# p/ \
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
- }1 F; f+ \) I3 [va_end(argp); /*结束可变参数获取*/2 ~( @7 Y. @. M0 U/ D% }+ Q
return ;
+ i* P2 o" ?) B# p, K% ^}: ]1 W9 p9 J. Q/ C/ T5 Y3 D. T
4 o- c; f0 P5 D5 ]8 L4)可变参数的使用需要注意的问题
/ b1 U K0 Q7 d* [! N3 | L9 g C" u0 q% D5 J% p' E
1.每个函数的可变参数至多有一个.2 @; b& D" i" @
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
' A. u- z i) M/ x) \3.可变参数的个数不确定,完全由程序约定.1 @5 D% l0 ]; X5 X; Q# p6 m
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
; _( S2 G0 w8 ?' T- i9 @而printf()中不是实现了识别参数吗?那是因为函数 , V1 r4 A" i+ ?
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
. c5 G: A- E: v- u6 Y) B5 C7 o2 t6 A的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
) W5 y, D( l/ ~" {6 N ? p过在自己的程序里作判断来实现的.
& ~( E0 ?3 s+ A. _7 ?: Q! _" u5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|