C语言可变参数函数_初探
一、什么是可变参数函数
C语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数,后面是数量可变的可选参数。
其中,强制参数必须至少一个,可选参数数量可变,类型可变,可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。
其实我们早就接触过可变参数函数了,C 语言中最常用的可变参数函数例子是 printf()和 scanf()。这两个函数都有一个强制参数,即格式化字符串。格式化字符串中的转换修饰符决定了可选参数的数量和类型。(是吧,printf中可以有自定义个%d,没毛病)
可变参数函数的参数列表的格式是,强制性参数在前,后面跟着一个逗号和省略号(…),这个省略号代表可选参数。比如:int fun(int, …) (我随便举的例子啊)
二、可变参数函数的实现 1、问题引入
我们先来思考这样一个问题,作为本节的引入:
如果我们要预先写一个可变参数的累加求和函数,其强制参数类型为int,用它来表示我们一共要传入多少个可变参数。于是我们大概可以有这么一个框架:
double getSum(int NumofPara, ...) { int i = 0; double sum = 0.0; for( i = 0; i < NumofPara; i++ ) { sum += ?? } }发现什么问题没有?
由于已经知道要传入多少个可变参数,所以求和思路就是,for循环遍历NumofPara次,每次都把sum加上一个可变参数。
思路很清晰,没毛病。但是,问题来了。由于是可变参数,我们无法提前得知可变参量的名字,也就没法访问这些可变参数。(你参数列表里都是一串省略号了,你怎么可能提前知道变量名,所以自然而然也无法表示、无法访问这些变量了)
2、实现思路
为了解决上述问题,C语言规定:
当编写可变参数函数时,必须用 va_list 类型定义参数指针,以获取可选参数。可变参数函数要获取可选参数时,必须通过一个类型为 va_list 的对象来进行访问,它包含了参数信息。
这种类型的对象也称为参数指针(argument pointer),它包含了栈中至少一个参数的位置。可以使用这个参数指针从一个可选参数移动到下一个可选参数,由此,函数就可以获取所有的可选参数。va_list 类型被定义在头文件 stdarg.h 中。
这么说可能太过官方,太抽象了。我们来举个例子。
假设我们有一个可变参数函数getSum(int NumofPara, …),然后现在我们代入具体值,比如getSum(3, 7, 8, 9),通过上面的介绍我们知道,第一个3是强制参数,表明后面跟了3个可变参量,而后面的7、8、9则为具体的可变参量。
根据C语言的要求,我们需要在getSum(int NumofPara, …)函数中定义一个va_list类型的指针。
然后它是怎么实现“访问、获取可选参数”的呢?
C语言里是这样实现的:通过某种机制(等会儿会讲)让强制参数和可选参数在内存中以连续的方式存放(强制参数在前),同时让va_list指针指向最后一个强制参数,即第一个可选参数前的强制参数。然后,通过另一种机制,每次访问va_list所指的参量之后,指针自动向后移位。进而当下一次再访问va_list的时候,访问的就是下一位的值。这样就可以访问各个可变参数了。大概就是这样了,讲得太接地气了。
3、具体实现函数
那么,C语言又是怎么实现上面提到的这些机制的呢?
由此引入两个函数。
void va_start(va_list argptr, lastparam);
是va_list指针的初始化函数,用来初始化指针,也就是实现让其“先指向最后一个强制参数”的功能。
于是自然而然的,该函数的第一个参数是一个va_list 类型的指针,第二个参数是可变参数函数中最后一个强制参数,即第一个可选参数前的强制参数。
va_start函数中,va_list进行初始化,指针指向末尾的强制参数。va_start结束后,初始化完成,指针自动移位到下一个参数,即第一个可变参数。(虽然感觉有点奇怪)
那么怎么访问va_list指针当前指向的可变参数呢?引出第二个函数:
type va_arg(va_list argptr, type);
其第一个参数是已经初始化完成的va_list指针,第二个参数则为可变参数的类型,返回的参数就是当前va_list指针所指的可变参数,所以类型也跟传入的可变参数类型相同。
每一次通过va_arg函数访问完一次参数后,va_list指针会自动移位到下一位。
C语言还规定,当不再需要使用参数指针时,必须调用宏 va_end来终结该指针,其实说白了就是释放内存。(如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针,也必须先调用宏 va_end)
4、具体实现示例
好了,说了这么多,我们通过完善之前写了一半的getSum函数来具体了解一下,可能就会豁然开朗明明白白了:
double getSum(int NumofPara, ...) { int i = 0; //用于for循环 double sum = 0.0; //用于求和 va_list pointer; //新建一个va_list类型的指针 va_start(pointer, NumofPara); //初始化指针,指针指向确定 for( i = 0; i < NumofPara; i++ ) { sum += va_arg(pointer, double); //通过va_arg函数来访问可变参数,返回类型为double } //同时,每次va_arg函数结束后,va_list指针指向下一位 va_end(pointer); //终结指针,释放内存 return sum; //P.S. 有个问题要注意一下,在main函数里调用的时候,应该写成getSum(3, 7.0, 8.0, 9.0),否则得到的可能是0。 }接下来简单补充一下上面提到的所谓“机制”:
它的实现原理利用了内存的压栈技术,将参数压入(push)栈内,使用时,再逐个从栈里pop出来
需要注意的是,压栈的顺序是从最右边参数开始的,再向左逐个压入,根据栈的原理,在取参数时,就从第一个可变参数开始了。
在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减。
(所以取的时候就是从低到高,也就是上面草图中从左到右从3到7到8到9)
三、不小心写多了一个标题
上面说得比较粗糙,可能有一些地方说错了。里面具体原理我也还没去深究。以后吧。
参考资料:
C语言可变参数函数
【干货】C语言可变参数
C语言可变参数列表知识总结
C语言中可变参数的使用方法
基于本文知识点,下一次再来追补一些关于vsprintf函数的知识点。dybf.ybt009.Com
dyyw.ybt009.Com
dypt.ybt009.Com
dyywz.ybt009.Com
dyksz.ybt009.Com
ksyw.ybt009.Com
kspt.ybt009.Com
kswz.ybt009.Com
ksywz.ybt009.Com
kszz.ybt009.Com
ksdz.ybt009.Com
ksfs.ybt009.Com
kspl.ybt009.Com
kmbd.ybt009.Com
kmzz.ybt009.Com
kmxd.ybt009.Com
kmpt.ybt009.Com
kmdj.ybt009.Com
kmwz.ybt009.Com
dyfs.ybt009.Com
dyfx.ybt009.Com
dyfw.ybt009.Com
dydz.ybt009.Com
dypl.ybt009.Com
dyzz.ybt009.Com
dyxd.ybt009.Com
dywz.ybt009.Com
dyzx.ybt009.Com
dyzb.ybt009.Com
dyrq.ybt009.Com
dyll.ybt009.Com
dysp.ybt009.Com
xhsdz.ybt009.Com
xhsfs.ybt009.Com
xhsyw.ybt009.Com
xhspt.ybt009.Com
xhsxd.ybt009.Com
wbyw.ybt009.Com
wbfs.ybt009.Com
wbpt.ybt009.Com
ywpt.ybt009.Com
ywwz.ybt009.Com
ywxd.ybt009.Com
ywzz.ybt009.Com
zzpt.ybt009.Com
zzxd.ybt009.Com
zzsc.ybt009.Com
fspt.ybt009.Com
fsxd.ybt009.Com
fsyw.ybt009.Com
dzpt.ybt009.Com
dzxd.ybt009.Com
dzyw.ybt009.Com
bfpt.ybt009.Com
bfxd.ybt009.Com
bfyw.ybt009.Com
plpt.ybt009.Com
plyw.ybt009.Com
rqpt.ybt009.Com
rqyw.ybt009.Com
zbpt.ybt009.Com
zbyw.ybt009.Com
dygm.ybt009.Com
dycs.ybt009.Com
dyjr.ybt009.Com
dyjy.ybt009.Com
dyqd.ybt009.Com
dygz.ybt009.Com
dyxh.ybt009.Com
dyfx.ybt009.Com
dyzr.ybt009.Com
dyhx.ybt009.Com
dyhq.ybt009.Com
dyjr.ybt009.Com
dyss.ybt009.Com
dyjm.ybt009.Com
dysh.ybt009.Com
dyhd.ybt009.Com
dyhy.ybt009.Com
dyjg.ybt009.Com
dyzs.ybt009.Com
dyfw.ybt009.Com
ksgm.ybt009.Com
kscs.ybt009.Com
ksjm.ybt009.Com
ksqd.ybt009.Com
ksgz.ybt009.Com
ksxh.ybt009.Com
ksfs.ybt009.Com
ksdz.ybt009.Com
kspl.ybt009.Com
ksbf.ybt009.Com
ksll.ybt009.Com
ksrq.ybt009.Com
kszb.ybt009.Com
kssp.ybt009.Com
kszz.ybt009.Com
ksxd.ybt009.Com
kspt.ybt009.Com
kswz.ybt009.Com
ksfw.ybt009.Com
kszx.ybt009.Com
kmfw.ybt009.Com
kmzx.ybt009.Com
kmrk.ybt009.Com
kmgs.ybt009.Com
kmwd.ybt009.Com
kmqd.ybt009.Com
kmpt.ybt009.Com
kmzz.ybt009.Com
kmxd.ybt009.Com
kmfw.ybt009.Com
kmhy.ybt009.Com
xhspt.ybt009.Com
xhswz.ybt009.Com
xhszz.ybt009.Com
xhsxd.ybt009.Com
xhsfw.ybt009.Com
xhsfx.ybt009.Com
xhstz.ybt009.Com
xhsgz.ybt009.Com
wbyw.ybt009.Com
wbpt.ybt009.Com
wbfs.ybt009.Com
wbxd.ybt009.Com
wbzz.ybt009.Com
ywfw.ybt009.Com
ywpt.ybt009.Com
ywwz.ybt009.Com
ywxd.ybt009.Com
ywzz.ybt009.Com
ywsc.ybt009.Com
zzpt.ybt009.Com
zzxd.ybt009.Com
zzsc.ybt009.Com
zzfw.ybt009.Com
zzyw.ybt009.Com
fspt.ybt009.Com
fsxd.ybt009.Com
fsyw.ybt009.Com
fszz.ybt009.Com
dzpt.ybt009.Com
dzxd.ybt009.Com
dzyw.ybt009.Com
dzzz.ybt009.Com
bfpt.ybt009.Com
bfxd.ybt009.Com
bfyw.ybt009.Com
plpt.ybt009.Com
plxd.ybt009.Com
plyw.ybt009.Com
rqpt.ybt009.Com
rqxd.ybt009.Com
rqyw.ybt009.Com
zbpt.ybt009.Com
zbxd.ybt009.Com
zbyw.ybt009.Com
dybs.ybt009.Com
dygl.ybt009.Com
dytz.ybt009.Com
dytr.ybt009.Com
dytl.ybt009.Com
dyty.ybt009.Com
dytw.ybt009.Com
dylt.ybt009.Com
dyrl.ybt009.Com
dyre.ybt009.Com
dyss.ybt009.Com
dyfx.ybt009.Com
dyfxw.ybt009.Com
dygz.ybt009.Com
dygzpt.ybt009.Com
dygzxd.ybt009.Com
dygzfw.ybt009.Com
dyzf.ybt009.Com
dyzs.ybt009.Com
dyzt.ybt009.Com
dyhh.ybt009.Com
dyhm.ybt009.Com
dyzr.ybt009.Com
dyzx.ybt009.Com
dyxh.ybt009.Com
dyzb.ybt009.Com
dyzbpt.ybt009.Com
dyzbyw.ybt009.Com
dyzbrq.ybt009.Com
dyzbxd.ybt009.Com
dyzbzz.ybt009.Com
dyrq.ybt009.Com
dyrqpt.ybt009.Com
dyrqyw.ybt009.Com
dyrqxd.ybt009.Com
kszb.ybt009.Com
kszbpt.ybt009.Com
kszbyw.ybt009.Com
kszbrq.ybt009.Com
ksrq.ybt009.Com
ksrqpt.ybt009.Com
ksrqyw.ybt009.Com
ksrqxd.ybt009.Com
ksll.ybt009.Com
ksllt.ybt009.Com
ksllyw.ybt009.Com
ksllpt.ybt009.Com
ksbf.ybt009.Com
ksbfpt.ybt009.Com
ksbfyw.ybt009.Com
ksbfxd.ybt009.Com
kswz.ybt009.Com
kspt.ybt009.Com
ksfw.ybt009.Com
kszx.ybt009.Com
ksqd.ybt009.Com
ksdzpt.ybt009.Com
ksfspt.ybt009.Com
xhs.ybt009.Com
xhswz.ybt009.Com
xhspt.ybt009.Com
xhsfw.ybt009.Com
xhsxd.ybt009.Com
xhszz.ybt009.Com
xhsyw.ybt009.Com
xhsfs.ybt009.Com
xhsdz.ybt009.Com
kmyw.ybt009.Com
kmpt.ybt009.Com
kmwz.ybt009.Com
kmzz.ybt009.Com
kmxd.ybt009.Com
kmfw.ybt009.Com
kmrz.ybt009.Com
kmrzpt.ybt009.Com
kmgs.ybt009.Com
kmqd.ybt009.Com
dykm.ybt009.Com
dykmpt.ybt009.Com
dykmzz.ybt009.Com
dykmxd.ybt009.Com
dykmyw.ybt009.Com
dykmdj.ybt009.Com
dykmdj.ybt009.Com
dykmwz.ybt009.Com
dykmfw.ybt009.Com
dydj.ybt009.Com
dydjpt.ybt009.Com
dydjyw.ybt009.Com
dydjxd.ybt009.Com
dydjfw.ybt009.Com
dyzdj.ybt009.Com
dyzdjpt.ybt009.Com
dybz.ybt009.Com
dybzpt.ybt009.Com
dybzxd.ybt009.Com
dybzfw.ybt009.Com
dyta.ybt009.Com
dytapt.ybt009.Com
dytou.ybt009.Com
dytt.ybt009.Com
dytl.ybt009.Com
dytlyw.ybt009.Com
dyqc.ybt009.Com
dyqcyw.ybt009.Com
dyqctp.ybt009.Com
dyqczf.ybt009.Com
