标题:
函数的可变参数详谈
[打印本页]
作者:
zw2004
时间:
2008-1-21 19:42
标题:
函数的可变参数详谈
可变参数的英文表示为:variable argument.
2 L* m5 w7 y7 l' G$ c, `
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
8 r% ]4 \5 G5 h* Z& S
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
0 o, w1 p8 s- Z+ d4 E# W* R2 j
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
/ o& C. C3 u/ B1 ^
实际的名称与之相对应.
# ]: I' I3 {/ m4 j
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
* K; V2 [2 c9 \% S. c
然而,更多地自由,同样也加大操作上的难度.
$ D, H( G' S' c7 D' u2 g3 o) L; V; c8 K
以下就对可变参数的几个方面作一定的介绍.
1 L b" |5 A- U7 _- \6 N& E
& K' _" O6 n& r6 r, b! v, G3 B
1)可变参数的存储形式.
) C. H* n! i3 w
2 s$ _' T9 ?* Q1 g
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
' M$ [+ M) F7 I) @
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
! p' g2 U. z3 X6 M: [- l
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
* W2 \# f! C0 h
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
6 l8 U# ~0 a; r5 M0 o8 @
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
2 o$ [- N& K9 V5 n( _/ s
栈区:
$ j9 q2 ^5 s6 Y4 N$ a1 \- T& z! R
B6 @* ?3 l$ V% q( [4 K2 z1 k
|栈顶 低地址
( K4 D/ K, I3 x, d0 Z" {4 h7 M- s' O+ {
. B& b9 |; G3 p/ s1 f$ j9 J* V
|第一个固定参数var1
, c) ~( g( ]# ]+ e
|可变参数前的第一个固定参数var2
4 h- p- [( E& n) D. _
|可变参数的第一个参数
$ e$ L0 `) f: M* P6 W( q
|...
) G. E# I, B- o) z8 }
|可变参数的最后一个参数
* t1 b0 W2 S* i' t$ ~& s
|函数的倒数第二个固定参数var3
. \; {* g" A+ L3 r1 ]& ^
|函数的最后一个固定参数var4
+ w5 }1 F6 Q# R' g( j2 a
|...
6 e, k# R* [, v
|函数的返回地址
+ j2 ~' x2 d9 v
|...
, t0 I. D$ J6 i7 k" f i, c) g
|栈底 高地址
, k. z) \; P" `5 S' @( r1 d# b
/ j5 \2 {- r8 d- N
2)使用可变参数所用到头文件和相关宏说明
! i: x* b% Y1 E" G4 S
8 \ P( ?4 K8 \& R1 W8 o
在此,以TC2.0编译器为参考对象来说明.
1 t/ ~# d1 U# A2 f r5 r$ b! y& r& V
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
2 a+ W+ q9 u# w4 r8 P+ P. O
此文件为:
) c0 {! @4 P: g/ F) g6 j& ?
/* stdarg.h
. J* o3 N; g- f" c; v+ V
1 u4 T" G) `7 O& k
Definitions for ACCESSing parameters in functions that accept
) S# Z5 }, U/ f$ [6 K
a variable number of arguments.
( Q; v1 Q, Q+ ]3 J. l/ L! @
$ P2 N0 i9 { P" i' S/ w
Copyright (c) Borland International 1987,1988
( v9 ?4 i$ Z* P/ p0 ~
All Rights Reserved.
( S$ F0 O: r5 V3 P/ ^$ X( t
*/
8 @' U9 T' L m& d9 B! [. o
#if __STDC__
5 t. y& z% Z+ `* D* l
#define _Cdecl
4 g- X) @. b$ {- ~% [+ I
#else
& B+ e' X" v, u0 }0 K9 P+ P9 Z
#define _Cdecl cdecl
- J1 L$ R7 `: k, h; P
#endif
- x+ s" B c* s
. n' `7 `% {$ v9 |& N
#if !defined(__STDARG)
; h7 m. F$ x& P J- k
#define __STDARG
$ D6 _5 ]; P' k7 J9 i8 v. R' \
1 @2 b- E1 O- W3 _
typedef void *va_list;
' h9 ]) C( j, K+ r. [& t
! i8 l5 ]) b1 [, E
#define va_start(ap, parmN) (ap = ...)
# b2 D8 A- ^- _2 W) B
#define va_arg(ap, type) (*((type *)(ap))++)
( k1 f6 K2 W- W
#define va_end(ap)
* R+ C' O! k% W
#define _va_ptr (...)
" y! _/ S4 {& K: Q" l' |
#endif
: g0 |( j7 o1 I; H
4 |5 W& O* g% Y( v+ f
以上为"STDARG.H"的内容.
( g# X& j! i' W% ~( u: M5 s4 C U3 N
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
{6 F( t+ @- _+ |
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
( c$ M( ]1 T0 d$ a
parmN为可变参数的前面一个固定参数.
, u6 [ Q" q/ _' b/ ?: o: @
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
$ f: K# C: B3 i6 }4 l l j
va_end(ap) 结束可变参数获取.
- M) P' b: g- V
3 C0 z$ e+ R0 H W o+ `4 l
3)可变参数的使用实例
2 Q: r$ B& U3 ]8 i# I+ p
, k2 o- m6 ?4 O( }5 a
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
5 M% }3 n6 T6 |0 p) R* [5 l h/ _
5 _8 g/ O( A7 T7 ?, ~
#include<stdio.h>
6 g; |$ R' Q. B3 H v
#include<conio.h>
6 \9 b6 c/ b7 S
#include<stdarg.h>
, j7 A) @* F0 w( _, D4 M1 S3 e
void tVarArg(int num,...);/*num为可变参数的个数*/
4 y( c' z+ A G
int main(void)
0 O& g' p$ v' T! @$ _! F k
{
3 X# p% u k' J# u+ L, K
clrscr();
$ l: v' ]% B; T) T3 v5 j+ r
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
. J- A ^* ~/ r0 L( |2 l `
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
0 G4 m7 ^3 t5 G! ]
getch();
f; w0 D; S5 ]% \. d+ S' b6 B' o
return 0;
& m) n- q9 ~2 F; ^! `
}
' ~- ?! h! d+ E9 D
void tVarArg(int num,...)
1 \/ j( E2 m) A$ I8 b" t4 X
{
7 D# p# M3 |! o4 ?2 c2 q; r
va_list argp; /*定义一个指向可变参数的变量*/
! X/ @ @8 @9 _4 {
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
8 d, O0 v0 I3 P
while(--num>=0)
+ C" o" \; f9 g3 B$ O
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
" Y- y6 u# [+ O1 a0 r3 q
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
% t2 h0 n" A1 N4 j
va_end(argp); /*结束可变参数获取*/
t2 U9 |- H+ N: j, v. g. y
return ;
) J% Z z. o) J# P. j$ t: e: D
}
0 h, H- L1 \# E0 B* s4 `$ U
5 r# c5 z1 O, v0 b1 s" w
4)可变参数的使用需要注意的问题
+ `$ D0 }1 X6 S5 }. X; y( `
7 {9 R! A2 Y3 [: y9 A
1.每个函数的可变参数至多有一个.
# [7 u* I/ s5 j7 N; Z
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
% E$ ` G. _5 ]% O3 _/ Y' c- L
3.可变参数的个数不确定,完全由程序约定.
- L/ i3 S8 T* E+ r
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
3 x6 p: W) F/ P2 W4 e s( u. ]
而printf()中不是实现了识别参数吗?那是因为函数
! d. H/ C# B _2 ^
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
( B0 C8 I# D; b
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
" `: l* Q# I' S2 G" `
过在自己的程序里作判断来实现的.
5 T- F& u6 k) Z0 q2 v
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.
欢迎光临 捌玖网络工作室 (http://www.89w.org/)
Powered by Discuz! 7.2