返回列表 发帖

函数的可变参数详谈

可变参数的英文表示为:variable argument.
( O8 |; w) A- j它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
' M! n! _, u$ ?3 @. y8 i: B可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不/ m2 L' c" K7 z# D# ^, C" B
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有/ m; ^9 j' {$ g
实际的名称与之相对应.
1 W( @; u2 e& [9 ]7 I$ O由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
- t. ~( Y- d: H$ L% P1 i然而,更多地自由,同样也加大操作上的难度.! A6 F( k" f1 p- h0 h3 [
以下就对可变参数的几个方面作一定的介绍.
& F( A2 ?3 ?0 J- P. F
4 ^- Y, _6 |. c" ~1)可变参数的存储形式.
: C, p0 t4 ~7 s/ d
- g8 Q( r, y( A, V大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,7 S" ^1 x. b8 P. I4 z4 H
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
* ^4 M; ?# A. Y  `/ L在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,: b) B' M/ e' b  n6 G4 z- d$ u4 h1 n
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.3 }! ]' Y8 Y2 \% C* q1 Q  s
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
! q  g3 N4 q  D) ~. d/ s栈区:
' }' y. y% l* f4 U$ B8 K! v- p8 s; ]' j, r' _( r2 ]
|栈顶             低地址' B- Q  I9 `, _+ p/ k6 V
' I- T+ c4 @7 S
|第一个固定参数var1; f; M8 b7 g. x- y% i& s9 j
|可变参数前的第一个固定参数var2: q. W' b! _. L* S+ ^$ Q! n
|可变参数的第一个参数
  e2 W9 x6 k0 i: Z/ K  B|...
2 @: S& p, x$ Q- Z|可变参数的最后一个参数
; s5 k+ }" R. W8 t9 Z3 b|函数的倒数第二个固定参数var3) j3 Z2 p" ?' S% Z
|函数的最后一个固定参数var4$ q% O, x9 ]- h, A- Z
|.... _: |$ w6 Q' `: i
|函数的返回地址" `+ v& m& P( P
|...% m6 `/ ~9 \% m6 n4 }
|栈底    高地址7 i8 ^* ?+ R+ }6 J- B
$ M& u/ p* }" ]" K3 e7 f; q
2)使用可变参数所用到头文件和相关宏说明
; M" n" ?2 O- U0 U4 @4 a1 @  V$ p8 o* P9 W4 e9 c* p# B
在此,以TC2.0编译器为参考对象来说明.
1 k" x0 b2 J3 p6 H" |) ?0 w可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
; e* n: A3 S' A5 f% z此文件为:( B+ w+ ^7 U% @% K/ E; g& l3 [4 r
/* stdarg.h* v- i- s  l. X: L% A

  _, l% Z4 \2 z* a5 SDefinitions for ACCESSing parameters in functions that accept
/ |3 H2 w! j6 Q5 ^+ Ga variable number of arguments.
6 \; Z% x; X. Y+ _3 n/ H, @, M8 T. }4 w( o( W: V6 O% ?
Copyright (c) Borland International 1987,1988! q$ R: v0 f' F# g" E
All Rights Reserved.9 f" Y& v4 ~  C! d1 P5 ^3 _. [8 a
*/5 ]; ^- P$ j* M+ r4 t, h% k+ t
#if __STDC__  M) S( z9 j  y' E* p* r
#define _Cdecl( k9 ~* ]! U" `$ D8 Q" e
#else1 D0 x9 N4 j! ]2 q* Z# `
#define _Cdecl cdecl
$ _  P, z: E3 Q8 r2 _#endif$ r8 H4 o; ?! r

% _, |2 A3 Q% G5 J( ~' R- x#if !defined(__STDARG)5 F4 D0 q! q" l2 E- `4 C
#define __STDARG
: z6 P" H/ D5 j+ o! o4 n- [
1 u. t3 ^( \: Q3 ~0 jtypedef void *va_list;
! N+ c1 ^! @; l( p( R: g6 N0 T
; Y, H3 d- z# H3 y6 D% ?#define va_start(ap, parmN) (ap = ...)
$ M0 m9 |5 j$ ], @. N2 t' I#define va_arg(ap, type) (*((type *)(ap))++)
0 ~7 p9 \" g3 j- [4 n#define va_end(ap)
5 j5 r. B& C2 B3 l#define _va_ptr   (...)
0 ]( j1 E  W$ {#endif7 A+ F# M% H/ v1 j
$ l/ s5 e  |3 X) q1 g1 E8 Q
以上为"STDARG.H"的内容.
+ p% R5 W) x2 _' R4 W- V6 j该文件定义了使用可变参数所用到的数据类型:typedef void  *va_list;# H8 k% W4 T5 @1 M0 Y, X! L
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
3 c# K- Q* O# i9 h  qparmN为可变参数的前面一个固定参数.
, s4 A( c" G) Yva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
( l# p9 [7 T% v7 Y1 e1 n6 q. {, qva_end(ap) 结束可变参数获取.
0 H/ o! y9 `% ^! A
! e7 o# v% i1 d0 D3)可变参数的使用实例+ K3 e9 k' N1 w& R

0 G, S8 p" o- }; r实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.6 W6 W- O! P; h. S0 c, v
5 W) K4 A" W4 [7 n
#include<stdio.h>1 c  m6 Z. `2 t* v6 M: A
#include<conio.h>
, N. Z" u4 z. t#include<stdarg.h>) w5 ]' b8 I+ P( H
void tVarArg(int num,...);/*num为可变参数的个数*/
; d* Q" \0 L% u9 jint main(void)
; Z2 k* \2 _6 p  M: c3 E% U{
7 Z# o4 b2 {5 G6 c. f  I5 h1 dclrscr();1 r  X1 Z' V/ h3 m2 q7 A2 I" q5 C
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");6 n; c6 n5 j2 x% _/ V1 C' N
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
, U- c4 {! s/ r5 |6 F; M7 w% P( dgetch();' x: G0 W" s& l) B4 @
return 0;5 m7 Z! ]& x2 b3 l& Y, S
}8 M4 }7 E3 H8 l" j
void tVarArg(int num,...)/ K' L& }7 c; ]  W3 ?  r7 p7 X
{
  {8 F2 ~( ~+ K, |) a  Ova_list argp;  /*定义一个指向可变参数的变量*/
* V1 ?( B5 P4 u) }, y: j/ Bva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/0 |$ V; T" {4 }3 D0 K
while(--num>=0)( M/ c9 b; L# Z& p9 _  b$ y
  printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,: ?; u5 f( T# U. P: X
    并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
" ?5 {4 K4 a! Y. p* _2 B+ \va_end(argp);  /*结束可变参数获取*/
0 R8 F# M6 A6 X7 y, z+ L. V0 kreturn ;/ T9 K- v% _; z3 P# d
}4 M) p1 d8 Y4 ?# Z& I

) Q5 N: _  \/ A4)可变参数的使用需要注意的问题- x  m" G) s. x. I/ N9 R4 C4 t
4 [3 S( [. ^4 Q$ X* }7 o. B
1.每个函数的可变参数至多有一个.
3 S/ W$ K3 E( R8 r2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.2 j: C' o  U$ t3 A4 m
3.可变参数的个数不确定,完全由程序约定.
+ s6 `6 F3 [' L4 I3 b4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.; E; c# l5 p6 @+ e
而printf()中不是实现了识别参数吗?那是因为函数
  o& W' Q; I# }7 B" eprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg . m- d9 t( x$ `: q7 e
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 # X8 l& ^) o3 n8 p' \
过在自己的程序里作判断来实现的.
0 ~$ r! d6 Q2 m. y0 d5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.

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