变参函数的实现

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

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

#include

/* minprintf: minimal printf with variable argument list */

void minprintf(char *fmt, ...)

{

    va_list ap; /* points to each unnamed arg in turn */

    char *p, *sval;

    int ival;

    double dval;

    va_start(ap, fmt); /* make ap point to 1st unnamed arg */

    for (p = fmt; *p; p++) {

       if (*p != '%') {

           putchar(*p);

           continue;

       }

       switch (*++p) {

           case 'd':

              ival = va_arg(ap, int);

              printf("%d", ival);

              break;

           case 'f':

              dval = va_arg(ap, double);

              printf("%f", dval);

              break;

           case 's':

              for (sval = va_arg(ap, char *); *sval; sval++)

                  putchar(*sval);

              break;

           default:

              putchar(*p);

              break;

       }

    }

    va_end(ap); /* clean up when done */

}

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

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

#include

double sum(int lim,...)

{

    va_list ap;                   // declare object to hold arguments

    double tot = 0;

    int i;

    va_start(ap, lim);            // initialize ap to argument list

    for (i = 0; i < lim; i++)

       tot += va_arg(ap, double); // access each item in argument list

    va_end(ap);                   // clean up

    return tot;

}

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

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

va_list

va_start(va_list, lastpar)

va_arg(va_list, Type)

va_end(va_list)

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中获取floatdouble使用的Type都是double,而获取charshortint使用的Type都是int

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

你可能感兴趣的:(C/C++)