标题:
函数的可变参数详谈
[打印本页]
作者:
zw2004
时间:
2008-1-21 19:42
标题:
函数的可变参数详谈
可变参数的英文表示为:variable argument.
% U7 V" Y1 e% }+ r9 B
它在函数的定义时,用三个点号'.'表示,用逗号与其它参数分隔.
L/ F3 k7 H3 [% H5 X
可变参数的特点:不像固定参数那样一一对应,也不像固定参数有固定的参数类型和参数名称;可变参数中个数不
. K1 H$ Y* b" \) t5 H. T% K4 F
定可是传入的是一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有
& ]: m5 g( g: P) G5 Z7 `
实际的名称与之相对应.
7 y" s: r8 f; O% T4 J o
由此可见,可变参数的形式非常自由而富有弹生.因些,它给那些天才程序员有更大地想象和发挥空间.
. D0 d# T7 K: n2 A9 S
然而,更多地自由,同样也加大操作上的难度.
0 R3 F) L9 a9 c9 @& K
以下就对可变参数的几个方面作一定的介绍.
* J5 x2 b5 c( W1 }
6 J0 n( t3 f+ T( T' E" |3 n" O
1)可变参数的存储形式.
1 r' p7 N( G3 ~! }7 D
1 E: I0 j3 T; r- O; x% {
大家都知道,一般函数的形参属于局部变量.而局部变量就是存储在内存的栈区(所谓的栈区:由编译器自动分配释放,
5 C& O! F2 T4 E [
存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。).可变参数也是存储在内存栈区.
+ n. @! q c' ~& p
在对函数的形参存储的时侯,编译器是从函数的形参的右边到左边逐一地压栈,
2 d, F/ x- k* g' W& T% o+ b
这样保证了栈顶是函数的形参的第一个参数(从左到右数).而80x86平台下的内存分配顺序是从高地址内存到低地址内存.
* ]6 q [3 n' _9 @4 F
因此,函数的形参在内存的存储形式如下图(以fun(int var1,int var2,...,int var3,int var4)为例):
1 ?+ G p1 f6 Z6 l) Q d' N
栈区:
6 m) e1 A8 R, [0 t6 ]
+ d1 r A% n/ I T/ i' E. b8 p
|栈顶 低地址
; G: A, i1 n0 u% |! @
6 L1 i8 t8 K& u+ ]
|第一个固定参数var1
8 b& b# }) w# ]% J* r* `9 E) E
|可变参数前的第一个固定参数var2
6 G+ y# ?( T ?; C# h
|可变参数的第一个参数
( Y) H1 ^2 W T# }# z7 _8 Z! Q( D5 i
|...
4 q0 B- w+ S Q9 U# ?2 _
|可变参数的最后一个参数
* f7 Y! L3 s( ?& f
|函数的倒数第二个固定参数var3
. U2 b0 l. }: m* F" T) P) Y
|函数的最后一个固定参数var4
7 U ?9 U8 w2 Z. ^1 Y4 P! P
|...
5 n1 z/ J9 k: b' f# e2 r4 m+ V
|函数的返回地址
! Q8 I m7 }- i! A/ Z% K
|...
0 I! O- r' _7 A1 R0 i+ D% E
|栈底 高地址
" U0 U3 P0 Q+ s; v
: Q0 y1 ~; H- P; B9 U# U m- j1 v
2)使用可变参数所用到头文件和相关宏说明
/ X v" Q/ \( d J; f2 Y6 U. C
: U6 r# }; g* j, t. e7 {
在此,以TC2.0编译器为参考对象来说明.
I/ d2 t( H: `) `7 X7 ~5 G `
可变参数的相关定义在TC2.0的名为"STDARG.H"的头文件中.
# s7 P% P6 ?: \. v1 s5 N$ ?' E
此文件为:
! a' z, W/ l! |! T8 @
/* stdarg.h
- O1 ~+ S' n4 \7 Y |! j( G
; T( ?% N8 Z7 o
Definitions for ACCESSing parameters in functions that accept
$ i, {" \ u9 I, V$ [* e7 A: D
a variable number of arguments.
, W- Z9 U0 N* z& j J- ?
- B& [) G5 E8 P/ @* b
Copyright (c) Borland International 1987,1988
$ _+ \/ P) Z( |
All Rights Reserved.
1 G* n2 l% B8 J: @6 M. r: L I, a
*/
: P& S" n3 O7 _2 m
#if __STDC__
7 P L- U6 X) {& I
#define _Cdecl
+ ?& X5 R' U9 Q$ ?" @
#else
; l' h) \3 v y
#define _Cdecl cdecl
/ c% H" F8 N3 O6 ?7 w
#endif
5 I% g7 j3 i- W& b9 j
, z4 a$ Y$ b; @! o7 h! u6 {4 P
#if !defined(__STDARG)
9 Y8 _* v/ Z5 f1 S/ \& l g
#define __STDARG
# `% J4 E, l! q# P7 P% K& Q
. p% ^0 F$ \( n9 m* P3 i
typedef void *va_list;
! T- z# R+ F# ~+ v& |
/ V8 K, g) R$ [% J
#define va_start(ap, parmN) (ap = ...)
5 \# n6 M6 M1 k: T
#define va_arg(ap, type) (*((type *)(ap))++)
: G% q- A% `4 P3 V+ a
#define va_end(ap)
3 p% e6 j a" V" Y
#define _va_ptr (...)
6 n8 I! x2 M1 @" C1 J
#endif
' ~+ H5 T4 [( ~: m
6 c0 f/ W7 z0 e, e/ a8 J, R8 q
以上为"STDARG.H"的内容.
0 t* c) k/ a% F* s1 m
该文件定义了使用可变参数所用到的数据类型:typedef void *va_list;
4 R5 S5 i4 K$ h& l2 g
va_start(ap,parmN)起到初始化,使用得ap指向可变参数的第一个参数.ap的类型为va_list,
; [" l+ a7 ?) S8 P2 @& P
parmN为可变参数的前面一个固定参数.
7 { y5 V# t e5 A
va_arg(ap,type)获得当前ap所指向的参数,并使ap指向可变参数的下一个参数,type为需要获得的参数的类型.
" P" c! h( q% C
va_end(ap) 结束可变参数获取.
/ B a- {( O: [/ E3 ^, B
/ P* g$ v- m' E2 h( M4 o
3)可变参数的使用实例
8 U( r1 p" p' j
4 K2 B# k) Z" K( a4 X8 {& Q
实例目的:用可变参数来实现个数不定的字符串的传递,并显示传递过来的字符串.
. w, z0 E! q3 r8 ^3 L8 L1 ]7 [
. b; C+ H/ f6 f" x
#include<stdio.h>
/ l- Z7 M- `, z/ R7 t9 [
#include<conio.h>
3 b& q T9 ~' P6 y- S
#include<stdarg.h>
0 m, [4 p1 [- N$ r8 \
void tVarArg(int num,...);/*num为可变参数的个数*/
4 B% L7 a: }. H& [ l
int main(void)
2 l1 }$ }# N0 d. [
{
1 u& B6 a* l d' Y2 ?5 W% L. g
clrscr();
8 J1 T4 L0 A$ e: c* M) e
tVarArg(5,"Hello! ","My ","name ","is ","neverTheSame.\n");
2 v! t# i+ S7 f0 Z5 g' ]! c' x, ^6 Q
tVarArg(8,"This ","is ","an ","example ","about ","variable-argument ","in ","funtion");
w: n6 W% M Z; X" k; Z* \
getch();
7 S8 Y# x! o4 n: w* ]
return 0;
; L, A' F; T8 v* E/ X# U6 R
}
8 ]8 r, F" k4 y- N( y! l: F
void tVarArg(int num,...)
?9 s6 |6 V2 r E% L/ @
{
1 G% G7 {7 F. Q2 R& }2 ?
va_list argp; /*定义一个指向可变参数的变量*/
' A7 |" |0 Y) v! M* q1 L
va_start(argp,num); /*初始化,使用argp指向可变参数的第一个参数*/
$ w# ~* L. ?, x& [
while(--num>=0)
6 B* e- b( `" t ?: @$ w
printf("%s",(va_arg(argp,char*)));/*va_arg(argp,char*)获得argp所指向的参数,
1 T* l$ }% V; n+ E4 o, w
并使用argp指向下一个参数,char*使用所获得的参数的类型转换为char*型.*/
6 r7 @5 S! J9 Z/ h) p- `
va_end(argp); /*结束可变参数获取*/
4 M& @6 H% c0 Y) g6 {
return ;
6 }$ E: D! m/ @/ v
}
( _' [& r. b- c( G: W: b! V
* Y- n; M3 t w* S x6 G
4)可变参数的使用需要注意的问题
: I5 M) \$ ]$ i7 }$ c# a
* u* R3 ?2 z0 |5 M0 @9 Y, B" }7 v
1.每个函数的可变参数至多有一个.
3 w) L$ L0 X# h' r
2.va_start(ap,parmN)中parmN为可变参数前的一个固定参数.
+ o% r1 S' x# [. ~8 \# K' p
3.可变参数的个数不确定,完全由程序约定.
: F. W, z. w3 b) r- i2 N2 M% b
4.可变参数的类型不确定,完全由va_arg(ap,type)中的type指定,然后就把参数的类型强制转换.
& @- `! p6 z; y; l5 C1 T/ [1 Q c
而printf()中不是实现了识别参数吗?那是因为函数
% E: g% _& Y2 N ]2 |) N* m
printf()是从固定参数format字符串来分析出参数的类型,再调用va_arg
1 }. C5 N+ H: u/ M, D; `; Y
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
/ t& P% b0 f) n! N5 }0 Z
过在自己的程序里作判断来实现的.
% L, l$ }7 G) X1 N2 R
5.编译器对可变参数的函数的原型检查不够严格,对编程人员要求很高.
欢迎光临 捌玖网络工作室 (http://89w.org/)
Powered by Discuz! 7.2