返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.
. [5 J* O+ d4 E" L. q% B% d它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.( o* [7 N$ g. y* N! G2 P
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不* l6 q( w! L4 Q1 l6 E% e: K% j
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有" H; N3 s6 f; p, ~. s! _: w. Z
实际的名称与之相对应.6 y* @6 W5 A; D" x0 {: G, I+ N# m. ?
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.1 `1 s5 |: N6 d/ g  R6 T
然而,更多地自由,同样也加大操作上的难度.
5 P- W" U! g- r' i, |* u" O以下就对可变参数的几个方面作一定的介绍.: h/ X* K" G6 a* t" u

8 F# H3 s5 s0 |+ n% t$ _1)可变参数的存储形式.
4 j( C; W! ^2 r% b' h9 Y( L- F2 j1 n, n* G7 u0 Q0 u  {1 w
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,2 @# q9 F& j, [$ Y& ^
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
) u; @! I8 o5 |) Y2 O; \! t  ?* a- ~在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,2 R& N4 q: U: C1 v% E0 v  F9 e
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.3 t/ X, g5 C2 a* J4 v
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
- Y, V: J4 X- N' s栈区:
4 |4 L/ ^- Z. J2 h
# C+ K" }3 y. U* _& q0 C|栈顶             低地址
/ H2 R$ m, S4 t0 }2 o) {: m$ t/ @; r4 K! Z9 [9 Z4 k
|第一个固定参数var1
- G4 ]! v2 [8 |: y0 V- T7 p! ~|可变参数前的第一个固定参数var2
' g4 Q3 Y6 U- [) K! V% e# a1 |- g: J|可变参数的第一个参数4 U3 Z* f' s8 B' R6 k
|...
% R# I: L4 i* v0 I1 z9 s|可变参数的最后一个参数! j( V2 I+ P- x" b+ o+ f* m  r; q
|函数的倒数第二个固定参数var31 g4 w# Y. {2 n% q" g3 n
|函数的最后一个固定参数var4
! C+ X3 U" S0 q  S0 g|...
6 \( m, M1 e, z: t9 k) {. @( q|函数的返回地址
- O6 [% O% b$ Y7 r|...& ]% e6 n( d# I+ k& P5 L, P
|栈底    高地址
" y& o9 C3 h* ~5 B/ s! T3 I" g+ @; @' k2 Z& _8 D0 L" f4 V+ f* H
2)使用可变参数所用到头文件和相关宏说明/ [: g+ v1 Q& ^

: A2 t3 O4 _! M+ ?2 V6 v3 B在此,以TC2.0编译器为参考对象来说明.
: }- X4 _- F' b, V可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.( Z8 c; ?% a# E$ p% U2 A! s# X1 Q
此文件为:9 x% h, q% V4 m0 {1 {, j
/* stdarg.h
% P% N9 }+ C" K! ?
( i* ?. [/ m, \8 g( ?5 A( TDefinitions for ACCESSing parameters in functions that accept
) R% P9 F1 S3 N# Ja variable number of arguments.5 x3 Y, Q/ h' B1 b  C  c# o
" \* V2 A9 s# d# t8 M
Copyright (c) Borland International 1987,1988
6 g$ m, Z7 c8 b  P5 J0 LAll Rights Reserved.7 x$ r& f4 v0 L  K' N& w  D9 t
*/
7 `5 B4 \* ^1 Q# e9 J6 l8 \#if __STDC__- u: T6 Y( q: x0 v8 F; w9 A
#define _Cdecl! ~' ?# z9 @3 `
#else8 b5 |8 W9 f3 j$ l
#define _Cdecl cdecl1 H& F9 l$ K- n# M+ W+ I; @
#endif
4 x* \: }# _2 q( Z1 d  x- b) K5 z5 \0 f" @$ u4 c, i
#if !defined(__STDARG)
6 ]7 ~. h. |& t: c* @#define __STDARG
9 v, p* c# H5 r: a4 u, f
9 ~9 G* a/ V* V2 {5 Dtypedef void *va_list;9 m6 u2 r$ F+ n. T6 n; @, X

0 q$ z! d5 i6 z: O- [& L+ ?8 k#define va_start(ap, parmN) (ap = ...)6 }! |/ k! N$ `
#define va_arg(ap, type) (*((type *)(ap))++)
0 W/ N& n3 Y2 Q" }7 c; Q3 Q- s#define va_end(ap)# r  N0 `( I, X/ f% m- D8 G
#define _va_ptr   (...)/ g- _0 z+ m, d9 ]$ `7 [# I5 \5 v
#endif: y  w+ O! y5 D, ?% m1 B. V
0 B' V& C& [' ^% L. C' O. j
以上为"STDARG.H"的内容.
$ r( E- ~) h8 T* n4 M' w" {0 j该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;* p" @7 X! T( c5 K
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
$ L' }# D: ?* w# M6 Y7 a2 CparmN为可变参数的前面一个固定参数.) E- u2 f" ]6 F) i( b: w' v# i4 W6 T
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
0 q+ P+ D6 f/ j; g. ^; r6 uva_end(ap) 结束可变参数获取.* q5 f) N! q; p5 M9 u7 J

' L& u' _. s' s" A% M$ ]; u! G8 y  `3)可变参数的使用实例3 z" {9 o0 n; V" {1 }* V* a+ K

- q# y7 @3 Y, p# M$ D, J+ I1 Q实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
" P' j% ~0 W$ _5 _8 a8 ]6 l0 C+ C+ x, a$ d# K. ]/ ^
#include<stdio.h>3 a0 u/ I8 [  T+ Q
#include<conio.h>
& A, I! I8 y& `0 ]#include<stdarg.h>4 M; _  ^( z- s0 }% {2 ~, ^
void tVarArg(int num,...);/*num为可变参数的个数*/  B7 u' ]4 ~. W& d  f0 K
int main(void)7 J8 M% R- y4 v  g% a
{
4 ]8 b- \/ X0 K/ [- P+ O% Aclrscr();0 v% K( F8 R+ w5 D: W/ f! A( X6 A  c
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
, ~* d  F0 u' ?; L* ~1 q6 WtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
- G3 O  t# U' K9 }5 igetch();
1 u3 w0 z3 C, _/ sreturn 0;
! N# N1 |, y  _9 k6 |8 W$ k}
- }' [9 ]* {. J$ {# x) Vvoid tVarArg(int num,...)
( O. y4 E: @. n  ~0 e9 h3 n" z{# J9 B3 ~: t6 A
va_list argp;  /*定义一个指向可变参数的变量*/3 {' l% d6 Z5 U( _5 j
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/4 M6 V  s& j  z3 r
while(--num>=0)
* q: P( H. @- T0 x  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,- x3 i9 F/ Y, V  h# O: k
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
3 p4 J: q' t. Z. cva_end(argp);  /*结束可变参数获取*/2 ^: T1 B7 Q0 L: D& y" d
return ;
% i2 K# _2 ~; a}1 n3 T. _7 _3 P$ t4 P+ e. u9 o: Z

2 f6 [  a- h- D( C' l) Q" |- ]4)可变参数的使用需要注意的问题) X9 B/ C/ O7 _7 ?4 u
: l. d  d& B7 G. g" G. k
1.每个函数的可变参数至多有一个.
+ S0 S5 g- H4 S" {/ w7 T2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.1 @3 {- Z0 R" X$ Y! `, [3 `
3.可变参数的个数不确定,完全由程序约定./ n6 D. E4 M' T2 Z
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.! I5 W+ L$ Z: r
而printf()中不是实现了识别参数吗?那是因为函数 5 l( N; ?% m) [! ?1 y
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg 7 \) d* H' x- _) W* w
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ! D6 K# G  v( A+ f# U  q
过在自己的程序里作判断来实现的.
9 Y9 p! g6 j+ i5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

返回列表
【捌玖网络】已经运行: