Board logo

标题: 函数的可变参数详谈 [打印本页]

作者: zw2004    时间: 2008-1-21 19:42     标题: 函数的可变参数详谈

可变参数的英文表示为:variable argument.
8 I+ u4 T! W' j: K7 Y# |它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔., z* o& j1 u% S6 {! i4 ], Q
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
. t' j, R5 S$ f6 [! a4 t; P# C% x定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有: w) C/ J. v; k2 ~  l
实际的名称与之相对应.
1 o8 V, c' L, a# G- z由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.; a& w4 j* p  ?: E. v
然而,更多地自由,同样也加大操作上的难度.
' M& d) H) n1 {" T$ G- Z. G以下就对可变参数的几个方面作一定的介绍.7 R; E" p, x/ c; k3 b

! P# m& x- j$ {7 x/ _# B5 i! q; T7 @1)可变参数的存储形式.
8 z) Y+ @4 J6 W( Y2 x. V/ h
1 Y! @) H3 b- W9 ^4 X/ }大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
2 m( D, X( @8 ]) u% j7 l存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.0 u2 G1 D3 k0 L; V7 n; W& r
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,1 }( k) I5 k; R7 T' w
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.  q% N3 M) f/ t# t- ]! S' O
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):% W& ?: c9 |; ^7 ~8 u0 |8 A$ i4 x' m& e
栈区:+ P3 q' q! U+ L: S9 [
) \8 ^; q0 k% H) P+ \* p& Z( o" @
|栈顶             低地址
& L* j4 m/ T. w3 L, P$ Y3 M9 M4 Q( Y
|第一个固定参数var1- K4 ]( L# _& x! _6 O
|可变参数前的第一个固定参数var2- K0 H6 K0 Y' s4 |  Y- Q
|可变参数的第一个参数3 d& |! g- Z+ s" T  U4 F: ?
|...; P* w( D% n$ y, K, T
|可变参数的最后一个参数
1 I7 N3 w* m5 _0 o  d2 |7 ?|函数的倒数第二个固定参数var3
5 q+ s( `; R3 ^|函数的最后一个固定参数var4. G* o* z4 V1 z4 ^
|...$ S, t2 O& A! m- h9 T  H. P, h: [7 `: V
|函数的返回地址1 |. F4 h! ?/ }' B7 O0 ]
|...+ D) A0 Y7 E" G( [! j% b" o
|栈底    高地址
( h& ]1 k6 J5 ^) Z5 R
+ a0 J5 w& x. t, J* Y2)使用可变参数所用到头文件和相关宏说明
( d+ D" F. H- V5 h* m% k# x  j% D" u, R! \0 j0 Q! b/ \
在此,以TC2.0编译器为参考对象来说明.
  ^. e9 l2 p3 _* [# o/ w可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.2 R7 \# n3 C7 C0 G( Z
此文件为:3 h; u2 K& ]2 E- C% [  e8 G
/* stdarg.h
' [- O; S3 n6 y! }3 m$ K3 w- T: j" \  q6 q# v  [& h9 L
Definitions for ACCESSing parameters in functions that accept
  G# D2 E! ^% h* H0 Oa variable number of arguments.) @5 I2 n3 G' X) t- U* w2 N

$ H8 c# \7 m6 Y$ n  r: P. k8 {Copyright (c) Borland International 1987,1988+ N+ \/ i; s: x+ r
All Rights Reserved.! v7 J; Y% [- r  l! ?) s/ b
*/
8 g. {* u. l: c/ O#if __STDC__3 Y! ]( n; ?0 Z: t% S1 [: i' ]* ~
#define _Cdecl
* A) X$ k0 y& U7 \4 w4 R3 E  x#else. `' s0 k6 b+ e8 K6 T9 F7 h
#define _Cdecl cdecl
- L8 L6 R4 b/ z9 l9 m' q#endif
3 e1 Q2 p3 f( Q2 i+ p3 E% C$ j* Y1 }  C  d, {/ z
#if !defined(__STDARG)' r' x6 U) k7 u' b* z8 N9 q- {
#define __STDARG
& l8 U+ h: |$ g! r% b3 O; t
/ N( u' I% M5 ]4 Atypedef void *va_list;5 L3 {6 f  g) Y7 [3 C7 d
7 T0 J8 o2 w, b
#define va_start(ap, parmN) (ap = ...)
1 C9 m' H6 c, }9 F" u$ U#define va_arg(ap, type) (*((type *)(ap))++)
3 x5 Z$ k$ e9 I+ l" J' f, c#define va_end(ap)% @$ l/ f2 H, J  P+ |" l  R6 l2 T* X
#define _va_ptr   (...)3 v) E- S" S% w8 }- r& d3 i/ a
#endif
0 x* i1 j! v6 Q  d/ L% p. p6 Q( p2 i
以上为"STDARG.H"的内容.5 O* o+ l, o* l+ D
该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
* [% v: G! K) Q/ ~4 b0 C1 J+ |va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,+ O% N! }' d: ^0 ^7 Z8 P, w
parmN为可变参数的前面一个固定参数.
% U  h( t$ X8 D! r: ?8 xva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.8 j7 M. f& c& Q. n) x
va_end(ap) 结束可变参数获取.
8 F$ p) T2 w* ^" B0 G) K+ v1 }* N- s, g
3)可变参数的使用实例
4 N: T7 a7 q7 x% _6 d* ]7 Q8 k+ P# u. D) f7 R
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.& \) e" ]" P3 g6 G5 |
/ A9 ~) L. T/ }; G, k
#include<stdio.h>
7 i: M( J7 f- c  d#include<conio.h>
2 y6 g4 z# V  f' W#include<stdarg.h>
$ U! {/ j% {1 L% g, bvoid tVarArg(int num,...);/*num为可变参数的个数*/4 N7 C' ]% s) V) y9 H0 W+ Q/ U0 P* N
int main(void)5 j, q8 y- w* B! A, i- h( i
{
1 W( n  I+ u; H3 m5 v' qclrscr();% t1 L4 Q  U3 N7 p( ^) ]
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
7 {* {& e; K7 [1 k$ wtVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");: G! E" b2 ^. B5 \
getch();- L+ G% W* b9 m9 u2 Z
return 0;
0 f& a1 j) E4 ?# Y8 y8 f}( T' N$ `( h% f  ]
void tVarArg(int num,...)0 q& N/ O' A6 |* G8 E2 |  s
{
! I; N) c+ r( N% W7 u+ mva_list argp;  /*定义一个指向可变参数的变量*/& i$ |, V  Z* u  U6 m
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
, J' s' E  N! Twhile(--num>=0)
, B+ c3 o. l$ _! N, e+ h  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
/ J8 G0 c7 h2 {. i/ F9 V    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/* m& N( T1 }3 L: ]" y9 V
va_end(argp);  /*结束可变参数获取*/
8 V" M3 l' J# I. m7 Z# Creturn ;3 Z: S9 S9 S* D  _2 d. r6 N  G
}5 P8 s) n2 P+ |5 J0 }* e, Z

/ [; h; f( Z7 w" G9 N. n4)可变参数的使用需要注意的问题
2 N; C5 o8 @  [% _  v* p% H, x6 f
, p3 ^& R. x( q6 H* `6 N1.每个函数的可变参数至多有一个.
4 C% n0 Z! b: o1 w2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.. _7 s3 B$ o/ E& K- n; ]3 a
3.可变参数的个数不确定,完全由程序约定.
8 \3 @9 W5 H# o+ a0 d+ E8 I4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.# a: z. G) @& _7 L: `/ ^" c
而printf()中不是实现了识别参数吗?那是因为函数
% ^, C0 v; @- C* uprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
: T) N# O2 c  L. x4 s- `( ^的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 ! K/ j; ^& i' C, L8 h* F
过在自己的程序里作判断来实现的. - Y2 a. j+ B& Z2 B4 ~
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.




欢迎光临 捌玖网络工作室 (http://www.89w.org/) Powered by Discuz! 7.2