返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.
, I" D/ P; J$ {$ c它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔." `. g1 Q1 M& l0 |! H7 }
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
% @+ A5 x2 N: S' Z定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
( y! o, t7 K/ _* ~+ l实际的名称与之相对应.
& m. H9 w: `/ T' x3 G' ?0 P# T由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.+ d2 f" K( M1 I
然而,更多地自由,同样也加大操作上的难度.
& z8 A7 U" A6 @- g+ N7 q以下就对可变参数的几个方面作一定的介绍.
, \$ t# ]' l2 _/ N* J% p8 s
" Z! L" s% ?7 n1)可变参数的存储形式.; U( @6 I$ Y7 r) F: ?" g( {

1 x4 G! L3 Y9 L5 M. ]3 H大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
* F0 g+ a2 {; y9 a存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.6 m6 t2 H* v5 {' ~$ R2 c( s8 L7 K
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,2 d7 D- N0 }: s2 d
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
1 ~! W4 r/ @7 ?6 A& M1 W因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):  _# g0 n8 n+ q1 p! L) S- C# Q. k
栈区:8 J8 r$ O. a$ r' R

. s0 \3 N( B- o$ a- B|栈顶             低地址
/ p7 n1 f6 M$ q
$ N4 c/ _# r4 R+ J6 d|第一个固定参数var1
" ~4 H# x$ R) I6 b2 h% ?|可变参数前的第一个固定参数var2" M, ]% F( b, N4 s5 [- B
|可变参数的第一个参数
' _! d' m' a0 z|...
& J  N3 t$ G* N: t5 N|可变参数的最后一个参数$ D2 q2 N. F& I7 ]- H/ }' `$ p
|函数的倒数第二个固定参数var35 ]+ ~& I4 K) a2 \1 m, V
|函数的最后一个固定参数var4
% n& T, p! O$ m- u. x|...
5 m  y: }* h, M0 u% ]  r|函数的返回地址+ }0 s, I5 a) B) k  P& F0 m" T8 H
|.... {: S6 H  M, B, b3 U
|栈底    高地址
  [9 d) a3 T- C4 \. M: c% E; H9 a! s+ \: z
2)使用可变参数所用到头文件和相关宏说明5 z/ a! k, U- L8 @8 W

# A7 ]+ o6 b" D- l$ h* U* [( ~, j在此,以TC2.0编译器为参考对象来说明.
1 g/ X" Q& \2 m可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
; Y* L% x& @# V) n0 h6 G& A; K此文件为:" x; t& J" e5 F# @! V6 V
/* stdarg.h
% @9 A" N. q9 W: ~8 \0 x7 [! E6 ~, m# I1 l( U- X( b8 I/ ^
Definitions for ACCESSing parameters in functions that accept- v! b/ M& N) z+ U
a variable number of arguments.1 s2 |' `, v* @* n* n* D- U

6 u3 R$ A7 D3 E& \- n& HCopyright (c) Borland International 1987,1988) w* C/ U$ z. h
All Rights Reserved." c$ n- z1 T3 o+ {( I! I
*/2 v0 b6 Q- r5 P4 w; q1 m$ L; T$ l
#if __STDC__/ x, m- w: E1 t6 y* e4 I- R  z
#define _Cdecl' i/ A5 B+ d; r
#else# }2 {0 d$ m0 D8 E. n
#define _Cdecl cdecl" d5 @. L& Z+ `, {. X
#endif
7 Y5 G  ~; C# i4 e& V
. q4 c8 P0 \% c#if !defined(__STDARG)
7 F% |' R; W3 W6 }0 x; O#define __STDARG/ s* N+ ?' `1 ?+ t( _. K
% J' ]) U3 u" D  l
typedef void *va_list;
. Q. t6 T! @( h7 p1 M  a  Y( T0 Y& l
#define va_start(ap, parmN) (ap = ...)# M2 J+ O, K% y: o
#define va_arg(ap, type) (*((type *)(ap))++)
: Z: m7 T! A$ b, ^) k#define va_end(ap)2 f. }- {1 E  `7 V, V7 [( l7 a; y2 [
#define _va_ptr   (...)- g2 C. W7 C3 z; b2 }, q
#endif+ k1 P  _# Y9 T3 l! j: \
9 o5 D! z7 u4 b* c
以上为"STDARG.H"的内容.
: O* C" h9 w) w% `9 V# X该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;
( U3 R* [) [9 r4 Y& c* g" N( a4 [va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
' c' _5 |8 E% i! e+ zparmN为可变参数的前面一个固定参数.3 |$ Y4 m8 x# v. c# S
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型./ `% @% T% O9 I" W9 ?; R
va_end(ap) 结束可变参数获取.
: E4 a/ E7 q. A" p; O0 f! o* N9 P; c$ j# w% z9 n* I0 J
3)可变参数的使用实例
# o: `% G. G2 I" {9 T3 y: u2 ^, E) ^; \7 J! x+ |* M
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.# W3 Q! [* L. w' e0 g6 p$ i+ ?
$ C  ~$ _8 b8 e1 [- F' n
#include<stdio.h>
" Q5 ~* x" Q* j9 @/ R( _+ q6 e#include<conio.h>. x6 t' @/ ~% C, P  S0 n' ?3 w% h8 k
#include<stdarg.h>( @! ?4 {" [8 P4 c
void tVarArg(int num,...);/*num为可变参数的个数*/. J' W* Y5 R! h% T: E
int main(void)
  T3 X1 u, {; b/ Z  s$ d{' ?" {8 A! _- N( h, `
clrscr();- {  ?1 @# B  }& H! M5 Z' ^( S
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
4 y+ {9 L7 a+ ^# m  z! l1 btVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
8 a3 g6 G' G! f8 ?. N% t! Hgetch();. i! b- E1 i5 g
return 0;
% I9 K8 r! U* O+ [}6 K& c0 K* @+ Z  [7 m$ C, B
void tVarArg(int num,...)  h9 G+ O8 Y( G
{
  ^3 J7 @, g  ?: p; Wva_list argp;  /*定义一个指向可变参数的变量*/% w0 X& h: y2 B( e9 i
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
6 M0 j9 M* V# Fwhile(--num>=0)' ~: \% m: G8 n1 v/ V# d' U
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,  d  E3 r  \: F$ f9 l1 T7 O
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
- _% z/ h, i# y9 j* h  o( q2 Uva_end(argp);  /*结束可变参数获取*/
3 D& x: ?5 ?# L4 C, H& J8 w3 wreturn ;$ d: N) t4 Z2 f+ J4 l
}
  }8 G- E. v; C# M" ?5 [
% Z8 T5 W; A! g, S6 Z4)可变参数的使用需要注意的问题3 @: B6 \1 h3 n# Z" Q/ E! Q1 ~, v
; ?& B. m# j& S7 \
1.每个函数的可变参数至多有一个.; r( r4 q. Y. p1 }  I7 J
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
; E: ~- o+ b, S3 j  Y+ ?$ W8 e  N3.可变参数的个数不确定,完全由程序约定.
. `8 B# t  _, J7 K% e4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.6 W& H: ]& u+ `9 j
而printf()中不是实现了识别参数吗?那是因为函数 2 |; ^# `! p* v3 t% R, L
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg $ f- I2 F" o4 L; G7 J# l+ S
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
9 `8 @4 ]$ E; ]- C: I过在自己的程序里作判断来实现的. 2 Y+ z7 @6 p" ~7 Z3 G5 ~
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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