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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
/ y+ J1 Z# {1 l7 [2 e$ z9 {! L9 `它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
; L( X, c* ?' M" y! r8 ^* o/ [可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
# B0 Q2 A+ ~: y4 \; I定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有% n% f: \* X. k8 Z6 c
实际的名称与之相对应.
& |4 o- G5 Z6 V& M- V4 d由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
7 z4 E0 G$ n% E# T) ?然而,更多地自由,同样也加大操作上的难度. d1 K! ]3 I c' v4 X
以下就对可变参数的几个方面作一定的介绍.
4 P$ Y. O. m% P4 M) f8 I r+ x% A3 l3 A! \( T. p* \: F; `1 ^$ x0 E. C
1)可变参数的存储形式.* p r; Q8 e1 k' f8 x) q
) a8 M$ k; C8 n% |4 {1 n5 \) l$ o2 g
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
! v% ]% X! I% S! K存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
! @8 u* }% x0 h5 h5 } O2 u在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
# d& S, i) n. u$ }' P/ s5 \这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
' o; c5 l/ q# D. l因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
: k1 H7 U8 M) R- q6 i J( D# f* {栈区:/ G& u0 h+ j4 ]1 y& |$ ^
+ ^" z9 S' j. }! s7 f- X|栈顶 低地址
1 j6 Q% G" O) K: L4 T+ z+ w! s, t' J: z
|第一个固定参数var1
8 h. T5 b4 d6 T. M+ Q/ |1 U3 e% _. L|可变参数前的第一个固定参数var2
0 R5 V; K2 B5 M|可变参数的第一个参数" c3 O% b5 s+ g& _0 D
|...
% k4 d! a! f& n3 b+ K% _) O|可变参数的最后一个参数6 w7 l& Z2 q/ K8 a- T$ Y
|函数的倒数第二个固定参数var3' x2 W% v; \( B
|函数的最后一个固定参数var4" v6 W2 X; w+ `
|...
6 r. ?7 r! l* P [8 O7 x|函数的返回地址
6 L8 N4 q8 I! P|...& `5 Q( x. ^1 \
|栈底 高地址
0 \$ \0 u- u+ g+ B- X3 ^) k; O& F
9 a3 x9 Y" {0 P' @2)使用可变参数所用到头文件和相关宏说明
1 K4 e$ N* n/ X, u0 K
2 B: @' T' ~8 t+ L3 s4 @0 z+ x" o在此,以TC2.0编译器为参考对象来说明.; y1 D( ?4 q+ a3 B
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
& Y" O" `5 n; B+ Z4 o% n9 J此文件为:% o* b! o* H, k p* a+ W
/* stdarg.h' F7 r' [& k; F7 M8 E9 r
+ e8 Y9 c% C5 S8 ]Definitions for ACCESSing parameters in functions that accept- d+ U; F) T3 f" b: Q' E- x, k( k
a variable number of arguments.
# g! m' d1 P* h) D8 X s; [" a& Q5 M; Q
Copyright (c) Borland International 1987,1988, }% K) j1 B( `; D |( l( Q
All Rights Reserved.5 @8 e) U9 S8 p" O* S/ V
*/7 C/ }6 ^% L* x: b) o, w
#if __STDC__* b1 I2 |# `. b
#define _Cdecl4 n& X, Z( e( h( {4 k: X" ~) u
#else/ J& f( ]9 p! @ i
#define _Cdecl cdecl
/ i* b- x9 w2 C: y9 F, ~#endif) k5 B0 B+ e6 E+ ~
3 c4 J. t' \# O( Q8 t9 N/ V X
#if !defined(__STDARG)
" K* f/ i" W% }7 p6 `#define __STDARG, a: z/ k' i V+ l$ L. z
' {# p; Y" p3 |+ w
typedef void *va_list;
& I# U1 @" L6 b* K9 K7 h _. q3 U5 k0 }" u# M
#define va_start(ap, parmN) (ap = ...)
6 J$ j+ L% n( ~#define va_arg(ap, type) (*((type *)(ap))++)
2 O* W6 Q5 Y- s( o% ]#define va_end(ap) Z/ O& }5 Z. a+ o- Z2 T) {8 D- {
#define _va_ptr (...)! T! m, b# }& I3 H
#endif
( ^4 F8 t6 h- t2 j4 `- h+ b" C2 A8 W) a6 V0 ~5 R" z8 S
以上为"STDARG.H"的内容.$ c, p' k5 H. L4 z3 l% E
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;. u/ s2 `9 K8 h- m0 s! H
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,& W) O8 r" B$ ~- J& y
parmN为可变参数的前面一个固定参数.
! h( O0 [9 T; l9 yva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.0 _$ ~( m( g; ]' E2 \- z5 y! l, b
va_end(ap) 结束可变参数获取.
" ]0 U- A) J9 a( V! W- \
) d9 P7 j4 P) s3)可变参数的使用实例
4 M M6 p7 L& j V2 T
- }- Y* f* s- \- {% Z) D# o实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
# c5 }# v5 c3 ]7 u
7 b; b' B7 h! `/ F- o2 s8 \#include<stdio.h>
) N3 k; r* C- K* e#include<conio.h>$ [/ z$ f% T* H5 N' i, g$ I: S
#include<stdarg.h>0 ?- W: {; A0 Z" O. x
void tVarArg(int num,...);/*num为可变参数的个数*/. a& G8 E$ y* C; N( c _# m
int main(void)& R# _9 L% } P2 A! K" Q% J
{
4 A5 m& D9 ]- m6 [* {clrscr();1 {* T- Y1 T& v: f7 |! W# J& b
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");# Q/ r6 d* l! `2 z6 E
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");/ O$ j, r7 g! f) M/ q! s) @
getch();, m: _; c( x$ Q( R# Q$ k
return 0;1 L" i* d5 E( ]1 c8 S
}
# u E& N7 p8 ~! K4 ~void tVarArg(int num,...)
8 l- F- e9 O) \- c) {, ]{
$ V! J9 \: K4 k: g* C# kva_list argp; /*定义一个指向可变参数的变量*/4 o. |3 S! y9 b9 T( \# \$ Y/ R
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/3 @% S* T& A, ~7 }$ J
while(--num>=0)! b% `* l: J8 ?! f( _
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,' g9 O" e" `. H. U. T& P
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
8 L' ]8 G4 ?' h" Z+ k( j, Ova_end(argp); /*结束可变参数获取*/3 o& r, v5 B1 J: ?: N
return ;) }, v0 ]8 V$ f4 T4 A
}
5 B3 e l5 U+ A1 L9 K5 r" v8 {6 [7 x. T- O4 m6 E. `- c2 Q
4)可变参数的使用需要注意的问题; G5 D( B7 r! H9 j) p6 L
. A$ _$ e# Q. S. I, s+ q. [! J' [
1.每个函数的可变参数至多有一个.
& U" }- V+ v" o2 e9 I% f4 T5 y2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.8 y. `- |! z& W8 m# s* B
3.可变参数的个数不确定,完全由程序约定.# k- B( ?5 u( }- n2 w
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.. m) J! _# i) X( q
而printf()中不是实现了识别参数吗?那是因为函数 % g" i. t+ A X( C
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 4 {; \7 a' @8 W
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
7 L ~4 o% N; d* U过在自己的程序里作判断来实现的.
5 o4 e% ]: V+ B s, }9 z. B. e5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|