可变参数的函数,写法如下:
type Function(固定参数1,固定参数2,……固定参数n,…);
可见,可变参数的函数有两种参数,一种是固定参数,一种是可变参数。
固定参数就是一定要输入的参数,其个数确定,类型确定。一般的函数都是使用固定参数。
可变参数用“…”来表示。其个数不固定。
一个可变参数的函数,至少要有1个固定参数。因为可变参数正是依靠最后一个固定参数来确定自身首地址。
可变参数函数使用va函数来对可变参数提供支持。
va函数定义在stdarg.h中,它们有:va_list,va_start(),va_arg(),va_copy(),va_end()。
①va_list:可变参数列表指针。调用va_start初始化后,该指针指向可变参数列表的第0号元素。依靠函数va_arg来调用列表元素并移动指针
②va_start(va_list对象,最后一个固定参数):va_list的初始化函数。其输入的两个参数分别为va_list对象和最后一个固定参数。使用最后一个固定参数来对va_list对象进行初始化。
比如
void Test(int n0,doublen1,char n2, ...)
{
va_listarg_ptr;
va_start(arg_ptr,n2);
……
}
其中最后一个固定参数是n2,所以va_start(arg_ptr,n2);的第二个参数是n2。这与最后一个固定参数的类型是无关的,固定参数可以是任何类型。
③va_arg(va_list对象, type):返回参数列表中指针所指的参数,返回类型为type,并使指针向下移动一个位置。即va_arg包含了两个功能:返回当前参数,并使指针指向下一个参数。
④va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。
⑤va_end(va_list对象):清空参数列表,并将va_list对象设为无效。
va_start()/va_copy() … va_end()是成对的。关于va_list对象的一切操作都必须在这二者之间进行。
如果调用va_end()进行了清空,那么调用va_start()/va_copy()即可创建新的va_list对象。
注意,va_list对象是无法获取大小的,或者说无法获取va_list对象中有多少个参数。一般来说,获取va_list对象中的参数个数有两种方法:①通过函数传参传进来②在可变参数的最后再加一个参数,为0、-1或空字符串。这样可以在函数内部进行判断。
例:
1.通过传参形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
int
sum(
int
count, ... )
{
va_list arg_ptr;
va_start( arg_ptr, count);
int
theSum=
0
;
for
(
int
i=
0
;i<count;i++) pre=
""
return
=
""
thesum=
""
><p>
2
.通过设最后一个不定参数为标志位:</p><pre
class
=
"brush:java;"
>
int
demo(
char
msg, ... )
{
va_list arg_ptr;
char
para;
va_start( arg_ptr, msg );
while
(
1
)
{
para = va_arg( arg_ptr,
char
);
//输入的每个不定参数都是char类型
if
( strcmp( para,
""
) ==
0
)
break
;
调用para
}
va_end( arg_ptr);
return
0
;
}
</pre>
<p>调用方法:“demo(
"DEMO"
,
"This"
,
"is"
,
"a"
,
"demo!"
,
""
);”,最后一个参数标示结束。</p>
<p>以上两种写法,都有个限制,那就是输入的所有不定参数都是同一类型的。因为va_arg(va_list对象, type)需要输入当前指向的参数类型,如果输入的多个不定参数类型不同,那么va_arg(va_list对象, type)的type参数就无法确定。</p>
<p>C使用的方法是由一个固定参数传入Char* pszFormat,该字符串中用%d/f/c等明确指明了传入参数的顺序和类型。这样,只要在函数内部解析这个固定参数,其中“%”的个数就是不定参数个数,不定参数类型依次解析%后的字符即可。</p>
<p>例:</p>
<pre
class
=
"brush:java;"
>
void
my_printf(
const
char
* fmt, ... )
{
va_list ap;
va_start(ap,fmt);
/* 用最后一个具有参数的类型的参数去初始化ap */
for
(;*fmt;++fmt)
{
/* 如果不是控制字符 */
if
(*fmt!=
'%'
)
{
continue
;
//结束单次循环
}
/* 如果是控制字符,查看下一字符 */
++fmt;
if
(
'\0'
==*fmt)
/* 如果是结束符 */
{
assert
(
0
);
/* 这是一个错误 */
break
;
}
switch
(*fmt)
{
case
'%'
:
/* 连续2个'%'输出1个'%' */
putchar(
'%'
);
break
;
case
'd'
:
/* 按照int输出 */
{
/* 下一个参数是int,取出 */
int
i = va_arg(ap,
int
);
printf(
"%d"
,i);
}
break
;
case
'c'
:
/* 按照字符输出 */
{
/* 下一个参数是char,取出 */
//不可以用char c = va_arg(ap,char);
int
c = va_arg(ap,
int
);
printf(
"%c"
,c);
}
break
;
}
}
va_end(ap);
/* 释放ap */
}
</pre>
<p>关于
'c'
的解析,用的是
int
c = va_arg(ap,
int
);</p>
<p>这是因为在C语言中,调用一个不带原型声明的函数时,调用者会对每个参数执行“默认实际参数提升(
default
argumentpromotions)”。</p>
<p>同时,对可变长参数列表超出最后一个有类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。</p>
<p>提升工作如下:</p>
<p>——
float
类型的实际参数将提升到
double
</p>
<p>——
char
、
short
和相应的signed、unsigned类型的实际参数提升到
int
</p>
<p>——如果
int
不能存储原值,则提升到unsignedint</p>
<p>然后,调用者将提升后的参数传递给被调用者。</p>
<p>同理,如果需要使用
short
和
float
,也应该这样:</p>
<pre
class
=
"brush:java;"
>
short
s = (
short
)va_arg(ap,
int
);
float
f = (
float
)va_arg(ap,
double
);
</pre>
<p>总之,va_arg(ap,type)中的type绝对不能为以下类型:</p>
<p>——
char
、signedchar、unsignedchar</p>
<p>——
short
、unsignedshort</p>
<p>——signedshort、shortint、signedshortint、unsignedshortint</p>
<p>——
float
</p>
</count;i++)>
|