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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
6 G) \0 u% V* i0 R它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
! P3 w' y: R4 q* a, ^. r+ i! A6 V可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
7 z( N6 h, Z6 p# O定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
* D8 S8 n: ^- r, \" f: X6 ^实际的名称与之相对应.
+ M- L) K6 h* L8 z( F6 [; u由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.' s7 o f/ r; d# v, O/ P) a8 V
然而,更多地自由,同样也加大操作上的难度.. j* y$ R M9 j9 l/ }
以下就对可变参数的几个方面作一定的介绍.; h' X( j: N$ Z- N5 [, `% d+ J
8 x$ I$ v2 C5 [5 G c
1)可变参数的存储形式.8 a) @/ Z8 I9 N% Q
y! _; k. u+ P* A1 t1 X
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,% x3 \, [; R% Q* H' c
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
- ^& F" j) Q6 ?( ?1 O在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,. w- X& |) x* X" Y+ V' [
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
2 N! |' g; R6 b因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
1 g- [1 Y7 B! A栈区:; c. w/ h1 Y/ B, w7 }
( G7 z7 ~) m7 _! a6 w% q/ s% ]
|栈顶 低地址
% X1 r) X" n7 }- U, d- V
7 T0 C* y4 a- r2 V' f3 G|第一个固定参数var1
" ]4 N. ? w T8 w|可变参数前的第一个固定参数var2
4 J; U5 S- H: j1 h) E* z% D|可变参数的第一个参数! |3 r6 D' y) o3 s
|... {; K; t/ l2 B+ r( g/ X
|可变参数的最后一个参数
7 }) a) N( X, m1 y' }$ X. E|函数的倒数第二个固定参数var3
% f% A2 g# P' x+ H|函数的最后一个固定参数var4 q( Q$ y; E* a
|...
( h+ m- Y4 t3 i|函数的返回地址* u8 F7 _4 ]2 F* v. M
|...
, ^: y" @1 Q0 s- U|栈底 高地址
8 [: N( {1 X$ n2 M& {5 y
2 d9 j) A* c* M2 }2)使用可变参数所用到头文件和相关宏说明
5 G# e* ~2 A) Z. F5 Y/ Q c, T
) t6 E( L8 n+ e( y0 j在此,以TC2.0编译器为参考对象来说明.
" d- @% T1 S9 S& X* t9 Y可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.$ Z2 E% k- D, S3 }
此文件为:
4 @8 v' V" H2 @/* stdarg.h
m- _8 E. q( l3 ^: _" X7 T. F8 K
3 s+ v0 I. n$ g, NDefinitions for ACCESSing parameters in functions that accept
; H1 e: ]; y* p/ t: B4 ~! i' ? Ua variable number of arguments.0 A0 p; n. m) j$ I' q$ y0 B, \
0 X* |; L' B! K) ECopyright (c) Borland International 1987,1988# c1 E. ]$ b6 h3 V' E \% P
All Rights Reserved.
, O/ w) Z- a( R5 M5 q9 \*/
; g; u% J+ q1 y) u% n2 b# o7 e7 p#if __STDC__7 w( Y" h+ `- E: G) i4 r7 Z7 z
#define _Cdecl
$ ?3 W( A9 S# P+ \1 _. D#else; c2 B9 ^8 q6 {: W. }
#define _Cdecl cdecl; l* N: z7 W, a% {
#endif {( p; t( B* \9 Y9 c6 @
3 b8 Y2 ^/ y% c# e( u4 Z
#if !defined(__STDARG)
+ Z% f" g* P0 p( ~" z/ O. Y#define __STDARG( g9 `+ r) D% E3 r
3 w% b3 K% n4 O1 n* ]. A2 Jtypedef void *va_list;' ~" O4 `' x1 a% `( u& i
3 B. @: j* X! c#define va_start(ap, parmN) (ap = ...)
L8 s, v* ~2 T& V#define va_arg(ap, type) (*((type *)(ap))++)8 N6 u B2 I4 ^
#define va_end(ap)
2 |, E( Y1 }; t0 a: q#define _va_ptr (...)
- d4 o, Z; G. J#endif) U7 t; d. I% R: G Q
% Q; x0 h. g0 C
以上为"STDARG.H"的内容.- `4 m* C( q0 T2 u' l
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;* ?* g/ `+ }* V4 q9 F1 B/ Q' q
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
: p; i; d: g8 h2 k* t' SparmN为可变参数的前面一个固定参数.' F9 @: z, l, N5 O5 k' {
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.: }! y5 n8 }. F4 e$ f' d7 \
va_end(ap) 结束可变参数获取.5 T+ }& K3 ~9 t
* \! A0 F- k! x& ?3 l3)可变参数的使用实例
( F! n; C( m% r% B9 R' X) K
3 f# q m+ e" K实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.7 e9 E. I! O# y! b+ @( k. C
& v" N6 |2 G B3 z: p) {% Z8 e* j T#include<stdio.h>5 h7 O: e5 ]3 v
#include<conio.h>
3 h# C; C8 z9 ?" o" Y _; X#include<stdarg.h>$ x3 T& E) V D) K
void tVarArg(int num,...);/*num为可变参数的个数*/
& A9 h- c& v7 J6 |int main(void)/ z2 l- U( v" q s, H
{0 {; }9 I2 l6 }( }
clrscr();7 T5 @4 N; h/ f5 N( E2 {
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
/ y4 I5 v- @. R7 D w. q4 b/ WtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
/ q6 H1 @' N- @# s: I, p7 ^getch();
; I( ?* m! W# C2 ureturn 0;
" a' ~2 U) o8 \; P9 k3 N S}5 ~6 H. L. {! D) Y) U# r$ e C$ H
void tVarArg(int num,...)
# ?+ |: x# y6 t8 f8 `. I{
; k2 r, t8 f5 I2 I* x9 u0 zva_list argp; /*定义一个指向可变参数的变量*/
; c7 o0 D6 E! y0 x2 b% ]: T# Yva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/* o, k5 ^7 n" y
while(--num>=0)
$ A5 m! A7 I# [; h! R printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,7 W$ o' K& N$ H" c4 p' i
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/3 e2 R4 |2 l% l: u
va_end(argp); /*结束可变参数获取*/
2 e/ m; I2 q% O9 ureturn ;5 W( L V' d; R: }* ^; \2 k A* g
}
' F" `2 n* k3 `- R& v {, _7 P. [ M- {) x! Q& B! x
4)可变参数的使用需要注意的问题
5 d3 _0 r. x3 v, |( _" u6 c
0 P0 e, f, a$ U8 l9 Q1.每个函数的可变参数至多有一个.
/ ]1 l- M( C8 I1 n, [# o) @8 i2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
: y$ y% [ i9 d6 ^3.可变参数的个数不确定,完全由程序约定.
7 X* u1 F# h# X' a4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.7 Z! P. o. u5 p5 [! m' l
而printf()中不是实现了识别参数吗?那是因为函数 ( _ b* u) Y1 t2 ^' _+ N# \7 l, w
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 6 Y/ c% S3 Q! f9 J+ S2 F) r% n
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
0 L8 z% C% B+ T" L4 _过在自己的程序里作判断来实现的. . }7 g" ?. ]! P/ J- l
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|