函数的可变参数详谈
可变参数的英文表示为:variable argument.
2 d+ c& S+ O- i9 I- ?: d* R它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
# ~7 i# e; F" G- Z. n可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
) ?$ o/ Q" l# Y定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
% u7 c6 z9 |! v) @% J实际的名称与之相对应.
6 P- Y6 E+ h4 ^8 A. t由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.6 M M @' u3 @" n* g9 _
然而,更多地自由,同样也加大操作上的难度.2 p; W s& z3 b9 _( k
以下就对可变参数的几个方面作一定的介绍.' U6 z- V4 f' G8 k" P) S
9 n. Z6 A; P- g2 w
1)可变参数的存储形式.+ ?$ \5 t# ^/ G% ^5 e: t% I* ?
7 y- A: k" h+ y$ [( ~大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,/ J' ]# [& V3 |9 I8 ^7 P' N4 m7 o
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.1 A" t# R3 ]4 e$ `, B* K
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
/ j+ W( \" v" m2 D ^3 [! T$ y这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
8 d' M8 X4 Q! ~* \因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例): V# X/ U3 U1 m; n8 |2 l3 f
栈区:
( @- Q7 Q4 P7 l# g% P- ^/ X: I# C
) `; j! Y, j% T7 |4 z( H|栈顶 低地址
: T) L8 r# B4 L
: l: e/ L2 \! c5 V- c8 \|第一个固定参数var1
$ b1 `1 `# u0 [) W|可变参数前的第一个固定参数var29 s }/ z! v) A0 t8 A2 |+ @
|可变参数的第一个参数6 i, V2 O: l5 e g! B
|...( r1 d' Y6 A$ j3 T) [: w$ z
|可变参数的最后一个参数" |" S6 W1 v# S B" _. {$ s) E" _4 [
|函数的倒数第二个固定参数var3$ ^/ I- V+ p6 B9 @4 D; I( H9 W# G4 Z
|函数的最后一个固定参数var4
6 I: `9 r2 a: T|... c+ G* {. T0 k; M
|函数的返回地址
2 w* n& {: i7 U1 D! {|...
3 h3 q1 \) i: S: e& N& L9 m0 O|栈底 高地址% o) ?( Y: O, A3 l8 S4 k
9 |# h% P4 R9 X. y( r
2)使用可变参数所用到头文件和相关宏说明' p. X' U/ m$ D8 d3 M
( Q0 b5 _" \- ]; r- `
在此,以TC2.0编译器为参考对象来说明.
6 F$ x" N9 @6 a: e可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.* X2 t+ Z1 H# y1 e. ], J! r3 p
此文件为:6 P* A# N; q4 I6 \$ @ D
/* stdarg.h
+ y( H% _# M2 v! X$ S% C$ |
L+ {# K5 g Q4 W+ ?Definitions for ACCESSing parameters in functions that accept+ k# W7 f) k1 c! U( ?
a variable number of arguments.
, @. O8 l: `$ w: o3 @: a4 U4 b; X" Q) Q* Q
Copyright (c) Borland International 1987,1988* X- L, m" G- i/ c! M6 T0 a
All Rights Reserved.
& T- n2 P, y. D*// u. z' B$ b" ?# \' y
#if __STDC__
4 W5 Q. k+ O8 q* s4 t N& l#define _Cdecl8 Q7 P) _8 f6 |. T7 `7 A* H5 R( m
#else9 k, W: P- j9 s D
#define _Cdecl cdecl
! b1 L4 K1 y- r2 B9 _/ y+ t#endif I* J, F. }* W8 D; J
# u. X3 r& Y% k5 _7 B" y& D1 |4 u#if !defined(__STDARG)
# f6 x9 r( y4 B; k0 F6 h# | T; a#define __STDARG
! D0 S; Y6 R! _+ f, A' U! V) b/ S' Q3 S5 L
typedef void *va_list;2 T4 {3 m6 X5 j9 Z& R) R
# J# _$ u" z5 f5 e: D7 ?
#define va_start(ap, parmN) (ap = ...)" M8 c0 J3 A ^4 Q$ g/ Y0 o( T
#define va_arg(ap, type) (*((type *)(ap))++); |) u q/ B& C# P0 T% ^ M
#define va_end(ap)1 k$ {7 T% @+ n' s# ]- s
#define _va_ptr (...)
+ m0 n. B( b( G" { ^#endif
e0 s' Z1 [6 \# ]$ l
. S( o' m3 @' Y+ f2 \以上为"STDARG.H"的内容." g J, Y7 A! g- I1 G9 Y# {
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;, E# U9 t) y/ L3 ~) L; l" ~
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
8 G2 A" i2 F. ?3 t! a9 u1 f/ {5 [! e7 Q2 hparmN为可变参数的前面一个固定参数.
6 U4 {; [; S- z4 \" Xva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
4 ?3 O3 p9 q _/ L8 U/ w0 e- vva_end(ap) 结束可变参数获取.
% S4 s" O$ D! w% Z( g" P) o6 E9 |' g" {& Z8 Q
3)可变参数的使用实例
9 T# l) U8 ], u! J. K* w% v/ I* I$ l- z) O* f# T b
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.. H; M* V7 F" }* G. t
. L$ P; F5 q. `. H4 K
#include<stdio.h>
- q0 ?' F4 |; [! H! G#include<conio.h>
& l% L1 a) u( P#include<stdarg.h>4 W. K6 _( X; m9 e) b
void tVarArg(int num,...);/*num为可变参数的个数*/
- M" K. T: F: }0 Q6 v6 h/ E0 aint main(void)( A& J' G' C& w N6 x1 A
{% W( Y% T+ t- t
clrscr();2 y, O/ o' y2 V" d, B" [; Y4 |
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
y% I) B& h: z1 u9 atVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");* y5 Q* P) e( I) {. [+ Y7 L
getch();
: M/ |3 Z6 @( `return 0;
. N" v! U- Q2 M8 w}5 q; `4 j5 k" Y3 A3 J
void tVarArg(int num,...)) [* a4 y- J; E- R, m
{
3 s k/ J! T$ A4 Vva_list argp; /*定义一个指向可变参数的变量*/
+ [: Q, p( Q9 O8 V: R( P, D% G( l9 rva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/( p# C4 e- m6 O: A9 H
while(--num>=0)% L8 K; w/ \" y3 l% ~3 P9 p
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
* }( {) l$ T" {# B' k+ y3 f. G 并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
2 I! |2 _; X3 {0 g) @# r% x1 ]. Eva_end(argp); /*结束可变参数获取*/) Y) y7 |+ v b- | L' p
return ;. p: S" u4 [. m# H
}
( E8 A4 c* J+ v! g I/ V' E' O1 |9 E b' O7 G5 f4 M
4)可变参数的使用需要注意的问题
5 S6 m& ~ f5 `1 U
" |5 e/ W8 B3 O1.每个函数的可变参数至多有一个.$ V3 o& B3 o8 O
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
1 l$ S( `2 ~3 T; v' H3.可变参数的个数不确定,完全由程序约定.8 D3 C: C% Q+ h H4 d9 h, ]
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
, M! l! s' j9 m* m; }而printf()中不是实现了识别参数吗?那是因为函数
" p9 a4 `& f+ v1 S( eprintf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
' S" Z* z4 {" L: x/ s' V# c) I的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通 * H; j; ]1 G+ z1 C! b; z( n
过在自己的程序里作判断来实现的. $ P8 |6 k! I( J& b7 J) h
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.