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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument./ C& }* d" o5 ~5 c! Q& J: r/ s
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.% J& {/ S/ l$ M, g7 l$ a+ S$ e* A9 a
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
# \9 H9 T- r* B2 m# F9 J% |: z定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有/ N% K4 x3 ^8 L, T/ D; n" s
实际的名称与之相对应.' c$ m* p* h0 K# d# U
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
0 l/ J0 X# B! x% ^: C$ m然而,更多地自由,同样也加大操作上的难度.
9 A- I2 q9 F% z- n2 `4 j以下就对可变参数的几个方面作一定的介绍.
4 z6 ?! S& T2 z; I. ?) h4 V+ X5 i0 X5 y# i+ |: o
1)可变参数的存储形式.# N2 \$ _" C* d, \# h5 U
k$ c# _+ G8 ~9 q4 i r( s
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,9 |9 u# T% T; ]; r. C" `6 t
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
. b' R" D# Z0 s. R u/ Z, I在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
# O" U. b& N4 T; S1 U2 W这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.) c4 w5 {4 j h
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
3 g, J) m% Z7 d$ _# p* k6 G: x) p栈区:
0 N- U% |- w: U2 L7 x
A( H. y! r% o2 y u|栈顶 低地址0 _& M( V# P& n: j. r/ {
2 {. p& R ]1 ]8 T5 T0 \% O6 v3 L
|第一个固定参数var1& Z3 v/ |% `4 L8 ?
|可变参数前的第一个固定参数var2( t5 w; ]- b* s
|可变参数的第一个参数7 [) x3 S( ?6 ]! H# _6 F
|...
/ ~8 J" T, z& `# U% s7 E* f|可变参数的最后一个参数9 A& Z2 N! d# g x
|函数的倒数第二个固定参数var3* j- O5 G- E* e& U" N* d: G8 z
|函数的最后一个固定参数var4
4 ]) ]/ ?" J8 C|...* n* I$ B }& W D7 W0 P' {( O
|函数的返回地址
4 y8 r* R& t$ Y+ B$ g4 `6 v$ H|...
, @ b* }8 z1 D/ C2 `7 e|栈底 高地址
9 U8 y4 \* P! Q0 R9 z" m' }, f) b% h; f Y) [8 f# i/ v
2)使用可变参数所用到头文件和相关宏说明7 B1 P8 S) G( b) I k$ R
0 p! Y2 T& [' F在此,以TC2.0编译器为参考对象来说明.
- m4 T9 X2 B+ v6 q& a$ P$ ^0 K可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.6 y" |) R- Z" \0 T) V9 h" {$ L
此文件为:
$ ]% ]; c4 R+ u/* stdarg.h
3 J* d- Q& {6 o0 Z6 x6 A- d5 H/ F
$ C* i# b# ?6 e4 T" L/ ] ]+ |3 CDefinitions for ACCESSing parameters in functions that accept8 G, T1 q+ x/ V8 ~% E
a variable number of arguments.
/ T3 h0 u0 @8 O r6 j3 C$ q$ W1 H1 m0 y& J+ N" D( ?+ K$ y" T9 ]; K
Copyright (c) Borland International 1987,1988, v+ y5 G5 p* `' ~# w- `
All Rights Reserved.
# Y ~9 `/ M- ]/ ]+ Y$ F6 X*/
& A) H4 f( T: x& V9 H: O#if __STDC__( B Q7 A Z' B* h( I# p0 P: P! R
#define _Cdecl
& d) ^8 i! H+ ^#else9 D. u+ r/ D2 c6 C. T
#define _Cdecl cdecl
! X" V8 j( n& n2 a w4 d#endif: l! v$ E1 C# l+ M1 N! r' l) w( w
' a8 @7 L! U% h- m# z% W& N#if !defined(__STDARG)
/ j- D3 ?$ s- [8 p2 Q- |( k#define __STDARG
7 g" i7 V8 ~4 I3 F4 h/ V# V
0 j1 v, p& X) o5 C$ jtypedef void *va_list;. Z) o3 j/ G6 L% f0 i
9 s# |$ e( g5 G" V$ t( N: b#define va_start(ap, parmN) (ap = ...)1 Q* S o" M! F% L: E
#define va_arg(ap, type) (*((type *)(ap))++)* w X, G& |, b; m9 i
#define va_end(ap)# z" A9 t8 v3 D! X2 f" a- @
#define _va_ptr (...)9 j5 z6 F0 u' [
#endif
/ v9 S+ U y! U0 R" H) E# [5 X1 Q1 D s3 r- ~, B
以上为"STDARG.H"的内容.+ J$ E1 |+ [* ~& U% w3 m0 e
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;' N8 J1 u/ ]9 J4 g g
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
N1 b1 T# n( @% N3 g, A( N+ o8 w0 GparmN为可变参数的前面一个固定参数.1 L; g; D O" @
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.* g- ]; V, H. @2 n6 I- \
va_end(ap) 结束可变参数获取.1 O9 v. h# R8 m+ j, o2 i3 T W
4 K) }( x6 a& @3 M T( O8 x
3)可变参数的使用实例
# r' E6 R- V M6 c- Y& c8 ^( |9 }
6 g0 P9 d' B( x3 W* n实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
. Q: f. o+ S- ]+ {( l8 Y4 L7 |. q4 a3 e. J8 x, t5 G
#include<stdio.h>4 \+ p* U6 P+ j( R/ k1 R! b
#include<conio.h>! Q% ]3 l7 A d; `
#include<stdarg.h>
. A) m" v2 s4 [void tVarArg(int num,...);/*num为可变参数的个数*// r' J+ L/ c$ a, ^! J- a) ?. h
int main(void)
5 @$ c+ [9 y( M8 `( d' u& M{
3 ^3 V/ G5 W9 W" p+ D4 L- U b1 sclrscr();/ R0 x1 f* l) l6 ~4 G3 o, U5 L( A$ H
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
+ h/ U G- F5 C, E4 {6 p) y; ytVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");! i* I- m- K0 H( Z9 p, _- ]
getch();* G% Y) L6 Q. m2 N- d* p l B3 s3 E: z
return 0;
2 r$ ~+ u" w: ^6 w' ?) T4 d9 C}8 \: ?; a9 _" C3 E4 ~
void tVarArg(int num,...)* b- H/ j7 S) h" {
{3 C& g/ h( A A' Z
va_list argp; /*定义一个指向可变参数的变量*/* W3 r" W. X3 K6 `5 {
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
2 u4 X2 l7 K) l, r9 P* }2 W5 U3 kwhile(--num>=0): @6 p% I+ u2 Q$ H
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,3 G/ w. @2 ^5 F# q8 g( A
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/# c: |+ `6 q' q, X
va_end(argp); /*结束可变参数获取*/! m: b0 r7 `( w6 S* W* |
return ;
, W2 t5 b2 u( F9 z}
1 \9 g$ b) y6 b7 [. ^( q$ p0 h8 i; a* {4 C' k4 O4 b2 k
4)可变参数的使用需要注意的问题) m( V# h* Y" r" O8 \3 \9 O( z
( j5 F/ q2 _8 n1 }* f1.每个函数的可变参数至多有一个.( y4 W/ ?% F% T& J, X4 s. W
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.9 @6 j9 r, u: @
3.可变参数的个数不确定,完全由程序约定.# M& z0 }7 X8 i" s `/ H+ C) |
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.8 s: I& x8 x. Q
而printf()中不是实现了识别参数吗?那是因为函数
0 G* W6 U7 [2 L3 @2 S$ yprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
" Q6 C4 ?# \% F7 L5 w% w的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 - s- \- d f! s
过在自己的程序里作判断来实现的. ! t! p% g) T' y& @4 j! ]! r/ d" Z
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|