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

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.
/ C/ S1 M# z) t) h9 ]9 P0 y它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.& C& I* o+ s4 _* P) U9 H ~
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
) o/ F- B x. ^# r; `+ [/ }$ a% C定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
9 v2 I+ B8 v, c4 _2 a( o. ]实际的名称与之相对应.
2 h, E+ i# Q3 k% ]" M4 c: G7 k由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
! r) A/ w7 g1 X; a$ A+ H% d9 T0 e然而,更多地自由,同样也加大操作上的难度.3 B/ ]( q. g: g* f/ {
以下就对可变参数的几个方面作一定的介绍.! H4 f" [3 ]2 Y/ }" Z
/ x, q" [3 I. E2 B4 F2 s( [5 p
1)可变参数的存储形式.. w/ a6 l1 E; W
% v8 S0 p! I) H大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,# h. [. p6 s5 W9 H
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.: q, o" b9 @" w
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,; @3 I2 J9 ~9 n2 G4 q& F
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.- j/ G, x) C4 `, E3 v0 p# T
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
5 V5 ~6 ]: @# t3 F) S栈区:. A h- A5 M3 u/ d: O. o* ?
" V7 V* X9 l" m5 F4 ||栈顶 低地址/ m" l y7 l0 I$ ^$ `) O7 K/ T7 b, N
. G% I* u1 n0 I( y) F7 |; x, A6 g' B|第一个固定参数var13 g. z6 S" s( ~& R, m( T6 ?; j0 F
|可变参数前的第一个固定参数var23 F0 J6 q3 F1 V* v: t# d' J3 o
|可变参数的第一个参数
+ q6 O8 A' s5 ^, p& Q9 g; o|...
+ C9 s& E4 B% M% s1 e|可变参数的最后一个参数+ }3 K7 \+ t+ @+ K
|函数的倒数第二个固定参数var3) X k9 O& ?- X8 K1 T0 Z7 ?7 c. k( \
|函数的最后一个固定参数var4# x+ b6 R J/ L, r) S
|...8 @7 B3 x( S0 }$ v& k/ `1 m
|函数的返回地址
3 x$ v e' R7 ]( C- c/ [|...
) s9 O" ^, Y' U, ~! l, ~# [9 d, \7 k|栈底 高地址# c: c+ }/ W+ k9 R# B/ E
7 A8 W$ W% P+ o9 K: c
2)使用可变参数所用到头文件和相关宏说明, C! H8 `4 g* Q# m: @% J
+ n- M, `3 C5 @7 h在此,以TC2.0编译器为参考对象来说明.
1 y* U9 x+ n( V+ u可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
9 i P, H" J+ Q a) @4 U3 }此文件为:8 _2 p5 h9 j% R9 }% t+ Y
/* stdarg.h' Z" _! U( r ]/ I
2 o5 F+ ]2 G c& w- ^) Y4 P
Definitions for ACCESSing parameters in functions that accept4 u) X8 ]9 m& `2 r+ o [; N0 e8 z
a variable number of arguments.
' ?6 T7 y9 |9 M* ^5 y9 d y8 n1 t, V- T: U5 q4 I4 T
Copyright (c) Borland International 1987,1988, Y( W; i! s* h& `, O ?
All Rights Reserved.
4 c7 C( `# d$ H! c7 w/ ^8 D8 G*/7 m, F% J% e* s6 Z. ~
#if __STDC__' \; Z8 v" }$ x" Z9 O; c4 M' `
#define _Cdecl
% a7 G6 F/ [ ]6 E n2 j3 M1 m& c( K#else! R* a; d: ]0 a8 ^
#define _Cdecl cdecl/ `' ?# O2 V, k! U
#endif2 X- M% f+ Z7 r5 Z2 l
1 z- P4 F/ X5 r# ^9 e- V/ V
#if !defined(__STDARG)9 D, `& _. _- e+ l8 Q4 m
#define __STDARG
3 ^" z3 ?4 H! |3 W; G7 P v8 r7 w8 ^8 t# ?1 V k6 r
typedef void *va_list;2 W2 r$ E9 M* e( w
2 b4 e# ` f, x/ P7 u5 _#define va_start(ap, parmN) (ap = ...)5 ?1 e! M3 k& |# R$ f: A
#define va_arg(ap, type) (*((type *)(ap))++)* B2 a- M5 ~3 l1 B; l. j
#define va_end(ap)4 a: i' X. s4 w: j0 A! p
#define _va_ptr (...); }/ H4 I+ N0 j# ~
#endif
- k& F' v1 x2 a* m6 }; Z3 I0 ~, K' Y- m6 i8 A* l: M; b( D! Y0 d
以上为"STDARG.H"的内容.0 }, \$ S: S2 k- W1 ]6 z% J$ e, V
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;3 Q( z& z8 ?) K1 J |8 P% j
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
2 `8 ` r, J! zparmN为可变参数的前面一个固定参数.* \& E( x0 \% m: f5 p5 F: u+ V
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.9 v, `3 M* p* g3 p9 U) X
va_end(ap) 结束可变参数获取.
; R" M9 w; _' T4 B% F1 o: G& p( v) M# x$ t4 M
3)可变参数的使用实例
9 V+ w6 _: ] p1 j+ H
7 e6 I3 Z' T5 x0 x9 K实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
% N) y( l+ P9 W6 }# ]$ t: Z2 n N8 G5 b' h* [; R
#include<stdio.h>
5 T6 Z/ J G0 \" R, i#include<conio.h>
3 G3 Y6 C3 ?+ D8 @. r \% f#include<stdarg.h>. U; Y8 t% V t
void tVarArg(int num,...);/*num为可变参数的个数*/
! U5 }+ T) a0 N1 g B; w8 _int main(void)) u. Z: ~% j, [/ p) U* O
{
- i8 X4 |& n7 a. ?" Aclrscr();# j! I1 ]2 C! u) X9 h. T: o5 i
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
9 {! b, T0 ~8 a$ y- \$ P! `0 PtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");- d3 J ?7 H. |9 N c8 M8 _
getch();
2 O8 o& N2 R5 C( g6 B. V4 Oreturn 0;' s# H d) F2 W4 U
}, }0 c" A% _1 U$ v3 {
void tVarArg(int num,...)
2 x6 ]% L& J9 i{
/ l9 u6 z5 |1 C$ [$ I8 s$ y+ yva_list argp; /*定义一个指向可变参数的变量*/$ r/ u' R( x" |6 z
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/$ @0 G% x7 m7 p Y
while(--num>=0)
# L+ S' c+ v0 g5 O3 ^3 G printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,/ f) L1 V. f4 E( d5 q( p, @' ]! o+ T
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
, C! B) [5 s2 u0 J/ iva_end(argp); /*结束可变参数获取*/9 J* i: d% N* c
return ;
. G$ X) N& G$ O}
2 \ u% {2 E4 a" ?/ y3 _! R K
4)可变参数的使用需要注意的问题" a0 I0 o$ H" e* I8 U
8 O$ J& x' ]% ?1.每个函数的可变参数至多有一个.; R; q2 _% E x P* I: ]: h
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
5 n& k6 u; V5 K3.可变参数的个数不确定,完全由程序约定.' Q* v! @! W) ]: ^9 V+ i
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.: j" n1 K. ^0 F* g8 m2 Q
而printf()中不是实现了识别参数吗?那是因为函数 ; @! g: F7 T4 n
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
- W- |8 K k2 b6 x) F. W: w的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
7 v( ^# j9 `, H- n/ O过在自己的程序里作判断来实现的.
) R4 n r/ B3 H. Q" U7 N2 C9 V6 l5 Y5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|