返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.8 \3 t% O, S6 Y8 c; Q8 ]- r/ y- u9 o
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.1 [4 K4 w+ ]  G
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不& C0 D- N/ X& l9 ~, y
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有& b* u* r5 z; A
实际的名称与之相对应.
6 a  \& R9 F, }/ ]) q1 w& C9 `% X% W由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.$ }  h; D: T9 j" J, Z5 E
然而,更多地自由,同样也加大操作上的难度.
& [4 E6 r- {# u以下就对可变参数的几个方面作一定的介绍.
& B1 m/ U3 {" X/ ]6 Q! R' j/ u: n. I8 j
1)可变参数的存储形式.
, A+ \5 z0 @% k8 O2 q5 k. z6 N% e1 c" [. c8 r6 Z
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
0 K; m6 H; K% [, ]: C7 b4 J, f/ ~存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
$ {, O0 I1 W, ]7 o5 E在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,2 K4 Y& F5 u! S3 w; h9 O) u
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.( N# J8 g! U+ F
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
/ X/ u% ~: w( N  e, O栈区:
- e. L) w: T! J$ ^9 o8 P9 n) W* e
, j" S) g+ O+ ~% r6 c' C|栈顶             低地址* r& B8 {" ]; D! }# {+ ^  |) _

4 [: y5 U3 m9 C5 y4 {' X( Z0 U6 j|第一个固定参数var1" \' S( a4 N- K6 g- x
|可变参数前的第一个固定参数var2
- N/ t- j1 {7 b|可变参数的第一个参数
8 U; p9 |1 ?) ]# P|...% z, f  p9 l4 m3 \+ f
|可变参数的最后一个参数
) b$ y& S' f% h- X2 n3 Z|函数的倒数第二个固定参数var3
; a5 C( u0 ]3 O; [% C|函数的最后一个固定参数var4# m) J! t/ P. R, Z- N
|...2 z/ p2 Z( s' |2 J$ y$ M: o: D' m
|函数的返回地址
* ~$ b7 g0 h/ S3 A|...& h; e5 P( x+ E- I1 M
|栈底    高地址3 l2 }0 R$ E$ G/ S' G- r

: l( ^. b8 k7 L9 z2)使用可变参数所用到头文件和相关宏说明* |; N8 v7 v  p1 ?; v% y

$ F7 @9 H( G8 n4 Z1 c. a9 L在此,以TC2.0编译器为参考对象来说明.# t1 f! |0 @" o$ Y
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
/ ^4 d5 M2 S3 R6 z$ @3 e此文件为:8 F' d$ ?# b% q! p/ g
/* stdarg.h# n3 y% \+ a/ Q) S4 {0 @* l2 a
, s% c, I1 z8 N2 b0 A" z0 Y( j
Definitions for ACCESSing parameters in functions that accept0 X; U1 d$ K+ [7 B$ ?7 @
a variable number of arguments.$ b. b: I& O! t, A% K

$ n' B6 K$ y% ]. e; m  D1 xCopyright (c) Borland International 1987,19883 M6 n5 j& F0 t) f' Q
All Rights Reserved.# r( b3 j2 G& y3 S
*/" {9 d! s! m$ z+ W# Q1 p. q; g% h: ]
#if __STDC__& t! G. ~9 W$ d; U4 w
#define _Cdecl
& S9 q4 ^  v# q1 _#else
6 p, c4 [' C0 {#define _Cdecl cdecl1 Z* P# h. b; A9 M. V" \! W
#endif
$ P; H# C) u/ T; d
0 O" J) h' N2 {$ x  d#if !defined(__STDARG)9 Z; [0 @+ j- V1 {  }5 ^
#define __STDARG
+ ]. D% M5 @- P) }6 a/ [; @$ z' Z# [% {8 s
typedef void *va_list;
0 l0 x2 P- Z) t: x5 A8 c' {4 I: v6 s& g' g0 d
#define va_start(ap, parmN) (ap = ...)& u% e; U+ \2 c5 y  H2 A
#define va_arg(ap, type) (*((type *)(ap))++)
0 `: N9 Q: Y. R; I# R#define va_end(ap)
: K- U# ~) H1 u) B5 W' T#define _va_ptr   (...). ~, X; z1 S$ U. d0 j
#endif, q9 `1 M( k1 q

7 m( O8 |. \* z8 j- k. d: b以上为"STDARG.H"的内容.9 g( v7 ]$ Z0 Q- q% M: j0 a% m
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
6 J- f; H4 j4 d$ d3 v+ k$ r% Jva_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
( `8 ?3 ?5 ?9 T* Q# xparmN为可变参数的前面一个固定参数.
8 [) z# K/ D( `7 W7 N, pva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
2 \2 \& O6 p5 s5 }; }  j# Cva_end(ap) 结束可变参数获取.
; m2 y3 t3 s, p# ]9 q  l! Y! x9 w; u  ]
3)可变参数的使用实例; I6 }7 b  b' {" F) `

7 G- I6 x' X; G+ i实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
. o0 B7 W4 W" H! J: U0 B5 w1 Z, \8 [5 s3 A! N5 p
#include<stdio.h>( u& R; C# h0 ]: ]
#include<conio.h>9 o0 C1 y+ C0 g# P
#include<stdarg.h>
* |3 t1 I! @8 I$ Rvoid tVarArg(int num,...);/*num为可变参数的个数*/# ~8 D# D# U( l$ h# h
int main(void)
) W6 O0 b7 a5 n1 o; A  o: f{
" U8 u) H5 Y. P" w# k) ~# i  c* vclrscr();
; A3 t8 u" j6 ^+ c6 ^tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
6 f. w# G6 N, i; {; C* `2 O; otVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
+ d8 i  \/ `" o2 ^+ z1 ^getch();6 ]2 Q6 L: E" v0 `$ B
return 0;% Q4 `. X+ p6 H% E# f  c$ E4 ^
}, K! A4 q7 D. Y+ Y9 W
void tVarArg(int num,...)
3 O4 y* \1 b! g) D. G" ?{
- E0 z# L. ?  T+ K# x" |5 h& wva_list argp;  /*定义一个指向可变参数的变量*/( E. S. K- Z- q1 @+ _) }
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
0 I: Q' c4 v6 Zwhile(--num>=0)+ N5 U5 U" e/ ^
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
. z) }6 N6 p& I! ?0 k2 N    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/6 m0 a* D# \0 g% c: D4 q! C
va_end(argp);  /*结束可变参数获取*/
6 n5 ~9 V! X- O( l* ]% Sreturn ;; |% L3 w5 ]0 G2 B! o4 I
}
/ }% F- F) ?& l( ?% Z! l" C1 \6 f4 @9 J. `0 O
4)可变参数的使用需要注意的问题4 j0 {8 }, ^8 j5 [

/ ~! b/ F( I9 L- O5 g1.每个函数的可变参数至多有一个./ B* `, @* t6 X3 }+ V
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
5 ^  E3 E0 s7 w3.可变参数的个数不确定,完全由程序约定.
" s2 B0 E+ G7 Z9 U( b9 T0 r$ f5 ?4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
3 R+ l3 [5 p2 b. r而printf()中不是实现了识别参数吗?那是因为函数 0 o/ {) h  O, V+ t' Y
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
1 A  H, `: r+ L6 [4 \! T的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 6 E  H8 H) F+ [6 u' M; U
过在自己的程序里作判断来实现的. / Y+ S6 ^$ s9 K  Y5 M- u
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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