  
- UID
- 133
- 帖子
- 51
- 精华
- 1
- 积分
- 186
- 金币
- 55
- 威望
- 2
- 贡献
- 0

|
函数的可变参数详谈
可变参数的英文表示为:variable argument.. T) M% W! U5 p, {2 G
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.1 r4 Z8 q- m2 g0 P/ @
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不$ P: R8 O' |, q/ h: |
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
/ K* O* R# U$ M8 K( c实际的名称与之相对应.
7 G+ D; `: @4 V! p1 E" V由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.1 n8 K; c1 O: d8 \
然而,更多地自由,同样也加大操作上的难度.
7 f: E# e8 j: f4 ]+ e0 D; P以下就对可变参数的几个方面作一定的介绍.
+ V7 T" K9 X$ j" H# M
$ q& Q; e0 b8 ?& }4 V1)可变参数的存储形式.
4 ~% e5 V" z- G# {0 N. `9 _+ k* N5 s* q
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
( S! B) v& m1 U& K8 H存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.9 Y; D3 j3 W7 i5 v1 z1 P6 b
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,, b! s8 F- l( W5 D; q
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.7 n* b2 ?/ l8 s( f5 D) x" ]
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):) Q9 g; s) }. o( @+ }2 x
栈区:
0 e" p) ~9 c+ `, H' n4 R- B% w M
3 c! L( ]6 A( p; G/ O! J2 X|栈顶 低地址( [, @( x. f9 u0 S& O4 D# u" p1 z
6 g4 o2 U% _6 @! ~8 ^
|第一个固定参数var1
7 I; k( h% g* A# C: h5 v8 o/ F|可变参数前的第一个固定参数var2
2 ]- S' r7 {6 w|可变参数的第一个参数; x) W4 h$ w- F
|...
( _; o! d. s" O. o|可变参数的最后一个参数
/ u% U$ P/ }, x|函数的倒数第二个固定参数var3
6 Z1 }9 n, p" _* E4 i|函数的最后一个固定参数var47 h- F. J; P9 ]; U
|...
) s2 O! Q# y$ E+ K4 m& v, Z+ G8 T|函数的返回地址2 b& M- Q: H/ K6 M: o
|...
O7 f/ [' S7 ?. f' [& y% G" c|栈底 高地址
; j2 e6 d2 T5 _
: S4 ?+ \& n9 o& ^7 m/ k2)使用可变参数所用到头文件和相关宏说明
7 C% m! G7 l$ p- d* T) z7 u3 g2 [2 g' T8 R
在此,以TC2.0编译器为参考对象来说明." H$ Z& c; W! O' d2 E
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
3 m( g6 G) G8 s# `+ x此文件为:. ~6 h# Z* U$ U! |' {: R3 F
/* stdarg.h
% k# O1 a+ p8 F) r+ u2 C# F/ ?; B/ {+ q- o
Definitions for ACCESSing parameters in functions that accept
0 }1 C2 u* j/ S2 Y$ ]a variable number of arguments.
* d- V: Z6 s5 m" w; C, T! h1 T1 L! T( D! N0 r4 P1 y, L6 b3 @' Y2 s& H
Copyright (c) Borland International 1987,1988
! `, p) ]( S5 J G1 bAll Rights Reserved.1 e* |6 b1 M9 z, i* `4 m
*/, l2 G, k; {( g6 b w
#if __STDC__
8 k3 I/ T4 q5 V# k; }' N#define _Cdecl; ^2 b: F" {' v- i$ `8 m
#else
" b9 h5 e m }' E#define _Cdecl cdecl
# A2 R, a7 v6 y- ]+ J4 Q1 R' `#endif
8 l$ s& Z/ z& M% o' l
' _9 g8 I/ V, }- e. W( S H#if !defined(__STDARG)) n o6 Y, p# L% I
#define __STDARG- R+ z! Q9 i3 b- J8 T2 k
' j( }4 K" v9 g/ c. _/ Z8 N- I' `0 wtypedef void *va_list;
" Q, B: ~# g1 z; R1 k3 c( t% }! A
#define va_start(ap, parmN) (ap = ...)
1 q- \9 L5 _' c6 ^#define va_arg(ap, type) (*((type *)(ap))++)
8 @, @4 b% Z- U% J7 K" [#define va_end(ap)
^. M2 k2 }1 e7 e#define _va_ptr (...)
7 C& w; ~$ J7 Z+ z8 k- p#endif
0 O0 e+ W$ ~! P4 n0 N7 E% V2 `, E# |
以上为"STDARG.H"的内容.$ Y' U3 g4 a. l ?- ^4 r: T
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;+ p4 b1 Y) }9 O' c
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
3 X4 {( q2 X2 iparmN为可变参数的前面一个固定参数.
4 l; S# o& h$ p7 N- Zva_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
m" A f$ k8 R3 Qva_end(ap) 结束可变参数获取.
$ u3 ~; k* P+ ?- H) B# k; ]/ E. p7 S6 l5 Y! r5 D
3)可变参数的使用实例' Y! c( Y! o1 c
' E0 N# d6 u; X; N# b
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
5 D& f. ^6 ~ v- |5 m, q- o6 b, ` y& |
#include<stdio.h>
: r' D; T/ t } b% l' j _#include<conio.h>& s& |% I8 z/ d) u1 G4 u, _
#include<stdarg.h>
4 s6 Y, f& ^& g2 D* _void tVarArg(int num,...);/*num为可变参数的个数*/
* s4 I% w2 F) U: p6 b+ g% P! D' ]int main(void)1 c e B) r6 \5 Z0 i& o9 S# K( a/ v8 c
{
9 z5 I0 d) z# x$ N: l1 Sclrscr();1 L! k& B* C5 }3 H- O' r5 O# k% }5 g
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
% f& r1 L2 Q. ?8 W" O6 W4 ?8 ptVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
+ J& H3 s) s) T+ \. @, E; s& xgetch();7 l. O! e& Z8 [4 r
return 0;* b }/ ]2 ?, u, d
}
+ c* ]* Z' @. U' v7 Z. K9 F" gvoid tVarArg(int num,...)' `) V" G- D0 b2 b- v
{% R5 \5 u8 Y1 l+ _7 ~2 a
va_list argp; /*定义一个指向可变参数的变量*/
3 m% C6 c" Y+ u% t0 R5 P! ^" Uva_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/$ y6 X1 J9 u% [5 w( [
while(--num>=0)
8 p# Z l! W4 J: K. J7 }* E }: D printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,; C5 |. ~! _$ V2 A: Y$ S* ^8 S
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/* c- N; Z& k+ o' L( `# v
va_end(argp); /*结束可变参数获取*/* ~) `. m( ~6 |: x
return ;% y( F& D! H9 ~! P9 X+ e/ c
}
D5 L) L& Q4 r& M" g$ F
5 T4 ~$ D; a% B& v0 w( w6 t r4)可变参数的使用需要注意的问题0 J5 r* V) ]5 [6 f7 P: z
$ M L; s$ j/ c# q3 ^: W1.每个函数的可变参数至多有一个.
" u$ F L. ? \; S2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
9 Y9 r' E' y* p k0 b0 t3.可变参数的个数不确定,完全由程序约定.
0 S" x3 V6 O& ~5 b* g6 a4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.' a4 @% t0 G2 ]' ] b: }9 u- g0 W
而printf()中不是实现了识别参数吗?那是因为函数
; s. ~5 C* h7 _( P3 W% U6 ~printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg " E. o* B+ O0 h$ t0 ?
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
' z' V9 a. ?3 u3 R! @& ~' C过在自己的程序里作判断来实现的. * f- q: I5 x# N1 Z
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高. |
|