标题:
函数的可变参数详谈
[打印本页]
作者:
zw2004
时间:
2008-1-21 19:42
标题:
函数的可变参数详谈
可变参数的英文表示为:variable argument.
; }/ @/ X1 L9 ?* b0 z
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
+ ]9 ~2 c6 W# z- r7 C0 P8 c
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
5 { u' @) J. k0 L3 F1 U7 s6 U; T
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
. Y' C: [ D/ |! I0 w, ^6 ]
实际的名称与之相对应.
# x( g2 q% c$ Z$ }1 a8 v
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
- _& u1 ?/ V6 F; H; _! `
然而,更多地自由,同样也加大操作上的难度.
# q4 w' W- N4 A' D% o" a3 S
以下就对可变参数的几个方面作一定的介绍.
4 u; G8 z9 y4 ?& i
$ J, }) g6 ?" n" E1 u- _
1)可变参数的存储形式.
' U. Y/ d/ s6 r8 v. f( }
2 |4 R' d* O: }6 N. x
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
8 ]3 y' v( m' c% ?9 O6 H' V' G
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
( e% f# e, \- |( d3 r( k
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
6 N. x' t* u4 i
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
* U4 T2 L" w# g; A' U9 o
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
& U. E6 a) g/ G+ S0 U' S( U8 w2 X
栈区:
2 ~! W9 U, o4 H/ @
" c% E+ {3 }3 f; R
|栈顶 低地址
# d% v! M% }9 l6 z# G( T
/ u( j& x2 q3 J7 a, U. P2 X% U2 r, C
|第一个固定参数var1
# H8 i4 g" Y- ]- P+ ]
|可变参数前的第一个固定参数var2
1 B9 M$ t+ _2 r Q' x3 _2 Y
|可变参数的第一个参数
0 J& ~8 {; p" e2 {
|...
: Q( m2 V1 c* s D" k2 L: m
|可变参数的最后一个参数
: s6 `' h) Q3 T! n- E2 z
|函数的倒数第二个固定参数var3
" \, h" _3 B' @# W9 J, }) O: k9 [0 ^# Y
|函数的最后一个固定参数var4
! i3 ]$ _2 E7 _9 C% y
|...
{- b: q! |6 }# o9 L5 A$ z6 B' L
|函数的返回地址
: i' l9 n, h l
|...
& S8 b: u2 i! W; A) s' f
|栈底 高地址
; r! j3 G4 _/ s) H
, q! ~. G0 A/ f/ N# k' ^7 U& Z
2)使用可变参数所用到头文件和相关宏说明
7 q8 b# l3 O6 ?
* E8 s* n! g' }2 y4 |0 y6 b
在此,以TC2.0编译器为参考对象来说明.
& a a1 I1 a; p% k
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
2 }4 n5 a: X2 W3 V v
此文件为:
7 l/ Y0 F- e" Y' e/ }* r% c
/* stdarg.h
/ M+ C ~5 q9 H/ o, N' Q1 M4 V
+ l$ A" w. j! i2 s8 @; h( I4 C
Definitions for ACCESSing parameters in functions that accept
, H7 Y7 a$ @! X+ ~/ [6 Z
a variable number of arguments.
$ K# h; u5 S. z' L5 l
* s- z6 k+ @3 j! }& M& Z- O
Copyright (c) Borland International 1987,1988
9 T; b9 l3 z# k$ W* D, l* _+ [* e
All Rights Reserved.
% O4 C3 F: W/ ^# R i4 ?
*/
" j# O% M5 ?# x2 z" j
#if __STDC__
) o% P1 Q% p0 g: \) V& P2 r
#define _Cdecl
t$ [7 U5 f8 |, ]; W8 P& R
#else
( s! L1 u, _2 ]$ }" ?
#define _Cdecl cdecl
5 F" T r1 X) k r7 g2 v: f0 d& v
#endif
' s3 y! e) `6 t! W* J, O
- p( l: H# j6 j
#if !defined(__STDARG)
1 I6 }. }/ U2 ?' x h7 _6 o$ M% \
#define __STDARG
/ m" w/ C. e) q
( X% `. a, \0 i" I% B! X. [ p
typedef void *va_list;
, V# c5 O# t- V' N
' t* [) m) J. k$ O7 t, B
#define va_start(ap, parmN) (ap = ...)
9 D! D5 q' t6 U6 v n1 U) [6 B! N
#define va_arg(ap, type) (*((type *)(ap))++)
" C! [# E* H- u1 J: L' b
#define va_end(ap)
9 ]6 t6 o) ~' {6 c
#define _va_ptr (...)
. Z! D3 H! ~- |" v
#endif
$ |2 }. i4 b$ A2 w( X4 K p2 K
! @! e. D4 Y' a F/ r5 b
以上为"STDARG.H"的内容.
# z7 I- a! Z G
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
& }0 Y# z7 r( L/ P. q D* C$ s8 p2 S
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
4 }' ] D! A% j6 b
parmN为可变参数的前面一个固定参数.
3 L* \' W2 B( m* Y P& q
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
4 ]7 e% A0 ?+ u1 Z
va_end(ap) 结束可变参数获取.
Q1 m: [( z9 r4 W; D( u& N
9 A0 e- X7 |) O. D% `0 x9 N
3)可变参数的使用实例
9 H. K8 Q' D. b/ {% o7 a
% W0 q; E4 N3 v P' J* ]& o6 o; D
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
8 o% G7 f& A( h* P) s+ Q1 c& x
/ Z9 `. E h7 w
#include<stdio.h>
4 h0 F( C. D6 @; N7 ^7 n9 `
#include<conio.h>
" L% \4 t2 u" [* i/ \3 [
#include<stdarg.h>
; Q7 I( M2 v2 p5 w3 g
void tVarArg(int num,...);/*num为可变参数的个数*/
* I2 Q% h; A/ a( J2 Q( l+ j C- Q
int main(void)
" q- P: M0 x, m9 _, ?
{
9 ^8 \/ F9 o) i6 W
clrscr();
( G' @; [. E2 A0 W* K; a: d" M/ E
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
0 D2 L/ Z9 p0 Z0 E
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
( `! T( l' t2 r1 P. i" x
getch();
9 B& b: R& L; f' u7 a
return 0;
2 G, x" B; U# V
}
W6 N, r/ n! b9 U
void tVarArg(int num,...)
3 z- L/ Z8 }. A. l q
{
. V* d- @$ h1 i" ~# ?$ H$ Y
va_list argp; /*定义一个指向可变参数的变量*/
: G" x0 ? ^4 y4 B2 G& i/ l' E
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
$ E/ _) |6 S# H2 V6 X7 s6 \
while(--num>=0)
5 m* p' Y( e8 R# p4 q- E) b
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
, p1 ^ r* v$ L: o
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
9 f4 d8 s7 T8 L, T) D3 ]
va_end(argp); /*结束可变参数获取*/
3 d# j6 V: ^5 H7 L1 I$ E
return ;
# f) S* ^6 k7 u
}
5 ]; l- b5 |! y& p6 G# ^
6 p9 g5 l) w5 y/ n+ U
4)可变参数的使用需要注意的问题
4 A% Z# B" L0 v) \) i( T
" O! J9 O% U7 ]4 {3 s
1.每个函数的可变参数至多有一个.
3 a2 P& `" z/ h* `4 t5 J9 [- |
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
* [+ W7 u( X3 N/ D0 v
3.可变参数的个数不确定,完全由程序约定.
6 q8 N. j) i7 O, T; u1 `+ l8 }
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
+ D! g# T/ \0 Q; M1 v) @. G
而printf()中不是实现了识别参数吗?那是因为函数
3 S1 l* d/ O) D
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
0 c+ I$ [) q% j F8 a
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
. V2 |( c P) i" K( M0 U
过在自己的程序里作判断来实现的.
8 k5 L* d/ o* x: M
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.
欢迎光临 捌玖网络工作室 (http://www.89w.org/)
Powered by Discuz! 7.2