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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
* w9 V6 C! G0 Q" Y它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.0 A! o0 M9 H- e+ r; `# f1 {) Z8 Y
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
5 s, P0 z' F% _* x! w定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
+ r; m- G0 h6 J0 k实际的名称与之相对应.' @" U2 ^7 p2 K" y& e
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
- @. Y" s$ Z! \( c8 B) W: o5 Z5 }然而,更多地自由,同样也加大操作上的难度.
+ ]- ^9 V8 M0 H m/ Z8 N以下就对可变参数的几个方面作一定的介绍.
* T9 p; N7 M9 ?+ V( f& V: T. d. o- @6 h
1)可变参数的存储形式.: Z6 ~, f6 x( C& M4 t7 C
# g c+ U, U- O6 d! L8 c# V大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
& X6 C2 ~" m3 C存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.- J9 A3 v. o) z4 h2 D/ e* b
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈," t! d0 P1 K. p' b3 b4 P" G$ M3 N
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.1 N7 X' Z% T7 J" a1 ?2 i
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
+ | T/ J8 h, x8 C! K- K0 a栈区:# N: e$ D3 C; c1 }9 s# ~& |
( H. g1 g7 L7 A- P
|栈顶 低地址
& l. h) Y. W4 L" B4 F! j0 u& E* k6 J) A& p( [
|第一个固定参数var1) r" f H4 ^1 ^0 d* {
|可变参数前的第一个固定参数var2
; C$ ?* [& M. Z0 ~! p|可变参数的第一个参数
, E! G5 c6 ? i% ~|...
3 N- Z1 z" l, T3 v|可变参数的最后一个参数: r- ~+ m# U% a. D# j) T9 a& [
|函数的倒数第二个固定参数var39 b6 c n1 Z& J5 t9 g5 Q
|函数的最后一个固定参数var4+ X5 B2 B& l. B
|...2 Z, K4 E( E& ]7 L/ s! V
|函数的返回地址6 k0 S6 ]" F9 t# D3 s& k+ j
|...* X- t! ?$ v1 z l' E
|栈底 高地址1 i. J) X$ ^+ E9 M
8 }$ \% N9 M8 J0 E8 B2)使用可变参数所用到头文件和相关宏说明, W L6 l0 O9 Q% b1 \. G
+ ~8 X1 U7 h9 Z2 [. X+ \% T; s: f5 ^在此,以TC2.0编译器为参考对象来说明.
. `5 S( ]/ u; @8 C' z! u可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.. F) H* f5 {+ w8 }* M: W: {
此文件为:( H3 p6 z3 _7 D5 ?' O
/* stdarg.h' C" m4 l( `8 a0 h$ o2 x9 }5 G
. T1 |! z; k- I
Definitions for ACCESSing parameters in functions that accept
- I- D! \ n) S* I* v* Xa variable number of arguments.8 T# j/ ]9 Z% {# ^: s" i1 k5 D. n
1 b7 ]' Q6 c( a, b5 r+ nCopyright (c) Borland International 1987,1988! Q% ]# V% Y+ g& i: Y9 t
All Rights Reserved. L- n% W8 B2 L4 f) f
*/
+ A' ^3 M+ p, G: r#if __STDC__
1 k% ?5 j* r- q; o" W1 y#define _Cdecl: K( e. _. P, y2 ^( H
#else$ j4 A! b2 D1 P# s
#define _Cdecl cdecl
1 Z; `8 N) e* K a( I#endif
5 _- z3 ~# ?; |* m6 k7 C0 k# `+ y+ O% ?: G7 p: U
#if !defined(__STDARG)
, u; `0 q& c' l* Q+ N2 n; B#define __STDARG a6 |: L1 _, n- w4 a% J- r
- H9 n: c3 c$ f% X! q
typedef void *va_list; e, P$ {% N9 Z7 z
. n# [7 r7 J8 K/ \$ T4 N' A& c4 ~. K. n
#define va_start(ap, parmN) (ap = ...)
+ _3 C! E3 A! ?/ e- ? ^9 ^#define va_arg(ap, type) (*((type *)(ap))++)0 F0 W# H7 s# k3 C U) p9 }) @
#define va_end(ap)7 B1 { S) |' v% p% l* m
#define _va_ptr (...)" P9 h) ^( A w$ D) l2 Q
#endif
' i: X, l" l$ y: A4 w8 i6 P4 o+ {# \" @* h' u
以上为"STDARG.H"的内容.1 H' T& ~" X2 J6 Q
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;) h% v: n1 t7 D& E p
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
% X& h9 }& P' f4 w( ^parmN为可变参数的前面一个固定参数.
2 m6 W+ p, N! D4 m9 p9 ava_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.& J* v& Q9 {& b) P1 t* V8 \* @
va_end(ap) 结束可变参数获取.# U/ ?8 O! f8 q3 H9 _0 R
: Q3 P, t6 r; m' M7 y3)可变参数的使用实例- G% R2 A8 i! i" v! j$ x) R
8 q' e# o; b) g$ U- h7 i实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
( W( Y+ G' ~% U! v" w& W9 H* R8 N5 l( I) X+ i% E B3 `' Q& B
#include<stdio.h>" e2 }6 @ E. g: q- R; v7 G
#include<conio.h>- ]5 C% \/ H& g
#include<stdarg.h> q9 ^$ M5 B9 h( z
void tVarArg(int num,...);/*num为可变参数的个数*/
; Y8 Q; [- A7 K- |3 [% f" A" |int main(void)* F' d; h1 ?+ v- B7 G/ K9 k" A
{, P8 K7 T- T' w9 A% g: U/ \# W# y
clrscr();5 ^ Y4 k7 k5 W" V, }0 {
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");8 Z- N4 M: ~' s; X6 W
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");; W9 ^: x- |% L P2 T
getch();
/ \ Q1 B$ _6 Yreturn 0;
; j! d. t' G* B) c E6 f}
5 w( T9 V% O# c8 Y+ \& j' O& pvoid tVarArg(int num,...)# L5 b+ Z" P1 d0 Z7 G( }
{
$ T, |. n% l' A* r3 A! u. Nva_list argp; /*定义一个指向可变参数的变量*/" x: E( l/ B1 V7 [, m- H# ] F0 w
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
6 e1 V& _$ [3 J- _1 h* j1 e+ owhile(--num>=0)$ m# R% G- y% n- X4 {
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,+ k8 W1 j& T( A- h- |$ K, Z/ H' G
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
6 c$ z# `$ [) i2 Mva_end(argp); /*结束可变参数获取*/
+ m5 X: M; x7 X. M! w/ ereturn ;* u5 t/ v8 ~* m; ]7 S! R" x1 S. [
}
; S8 e- |- |2 ^. x- u* a
, _0 v& Q1 Q# m0 V4)可变参数的使用需要注意的问题$ P5 [" h, T" L0 g6 V$ u( |
1 N# a% |7 p4 q+ A4 r1.每个函数的可变参数至多有一个.2 a! x+ J; a. C L5 j/ X1 f
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.: @7 V: s4 _9 \ w3 [; F
3.可变参数的个数不确定,完全由程序约定.3 U8 H$ v1 a. m* w
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.' F" V; D( }, y
而printf()中不是实现了识别参数吗?那是因为函数
! K7 Q, ?, K8 [/ A% v7 {1 F" \8 i' rprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 5 ~, s) E6 ^6 S/ p) p
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
. Q& I4 Z/ n$ g0 J* p& f8 G过在自己的程序里作判断来实现的.
# H+ s) i! g1 |) q1 Q/ z5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|