首页UC › 变参函数的实现

变参函数的实现

相对于固定参数的函数,变参函数的可用性无疑是更好的。我们最常用的变参函数包括scanf和printf。刚刚接触到变参函数的时候,我觉得这太神奇了,它并不知道我要输入什么类型的数据,要输入多少个数据,却能完美地处理。其实,可变参数机制实现起来是相当容易的(在stdarg.h的基础上),而且,它的作用并没有想象中的那么神奇。

可变参数机制并不能获取某次输入的所有参数的个数,也不能自己确定每一个输入参数的类型。嗯,没错,看上去printf和scanf就能知道每次输入的参数个数和每个参数的类型。其实,仔细想一想就会发现printf和scanf没这个本事,输入的参数个数和每个参数的类型是使用者在format内容中,通过%模式等告诉编译器的。光是这么说可能不够取信于人,以简化的printf为例,让我们看一段K&R中的例程吧:

可以看出,printf处理可变参数的关键就在于它的参数char *fmt。注意程序中的for循环,指针p正是一次次地根据fmt中的提示(在这个简化的例子中是%)经由switch分支来确定下一个参数的类型和有效参数的个数。

所以说,可变参数不是万能的,它只是一种很normal的机制,不过正是先驱们那化腐朽为神奇的想象力,借由这种normal的机制实现了神奇而又令人诟病的scanf和printf函数。一般而言,除了像printf一样,在函数中实现一个特殊的format之外,一些可变参数中的参数类型都是一致的(比如说只可能都是int)函数,则会在之前的固定参数中用一个参数指出可变参数的个数或是类型。下面是一个C Primer Plus中的例程:

可以看出,函数sum中的固定参数lim指出了可变参数的个数。

以上介绍了可变参数机制实现的两种过程:自定义format和前置指示标志。接下来就会详细解释在C语言中如何具体实现可变参数机制。从两个例程可以看出,有四个宏是至关重要的:

va_list:一个char链表(实际上应该是一个连续的内存块,像数组一样),在使用时表现为一个指向char类型的指针;

va_start:初始化va_list。通过最后的固定参数实现对可变参数初始位置的定位,并为va_list分配内存,将可变参数复制该内存块中,使va_list指向该内存块的初始位置;
va_arg:通过移动指针va_list获取由参数Type指定的变量并返回该变量。
va_end:释放va_list拥有的内存块所占据的内存空间。
看,一切不就一清二楚了吗?不过,还有如下几个问题还需要特别注意一下:
1> C标准规定实现可变参数机制的函数至少要有一个固定参数。从上面的讨论可以看出,这无论是从语法上还是实现上都是必须的。
2> 隐式类型转换不可用。比如说,va_arg指定了类型是double,若传入的是int的变量10就会出错。
3> C语言的整型提升原则。也就是说,在va_arg中获取float和double使用的Type都是double,而获取char、short和int使用的Type都是int。

函数指针不可用,除非用typedef定义过。

发表评论