【C/C++ 语法备忘】4、可变参数列表

【参考】:

1、《C++ Primer》,page 211

2、百度百科

3、《c++ 可变参数列表 》 (未找到原帖)


一、可变参数列表的用途

     可变参数列表可以传递一组长度不定的入参。最典型的使用场景有 stdlib 的 printf,另外在cocos2d中也大量使用,例如CCMenu的create方法,ccanimation的创建之类的。

     不过按照我的理解,几乎80%的使用可变参数列表的场景,都可以使用数组来解决。但是使用可变参数列表的函数可以提供一种数组无法提供的东西:优雅。

     可以对比以下两种调用方式:

int main(int argc, char ** argv) {
    varInputTest(1, 2, 3, 4);

    int array[4] = {1, 2, 3, 4};
    inputTestWithArray(4, array);

}
     后一种对调用者来说要麻烦一点。

     在另一种场景中,可变参数列表的解决方案也要优秀一些,这就是例如printf这种需要传递一个长度可变、且类型可变的列表来匹配前面的通配符的场景。虽然也可以用void *来实现,不过同上面一样,还是用可变参数列表实现来让调用者更舒服。


二、C语言中的可变参数的限制

     和java中相比,C中的可变参数要弱不少,主要有两个缺点:

     1、无法确定可变参数列表的长度。这就是为什么printf要提供通配符,在cocos2d中要用NULL结尾

     2、不能提供类型检查。由于不能判定入参的类型,所以在从实参到形参的复制过程中可能会有问题,所以一般都建议只传递基本类型,或者是类类型的指针。但是这一点意外的也有一些用处,比如上面说到的printf


三、几个关键的宏

    需要 #include <stdarg.h>

1、va_list

    1)用法

va_list args;
    如上,定义了一个 va_list类型的变量 args,可以用该变量作为保存可变参数列表的指针。实际使用中, 如果把可变参数列表的入参看做一个数组 array,那么这个 va_list 就相当于其迭代器 iterator

    这个宏本身只是个定义,真正赋予其意义的在于下面的几个宏。

    2)实现:实际上只是一个 char * 类型的指针,原因这里不能判定类型,所以用size为1的char类型指针会方便移动。


2、va_start

    1)用法:

void varInputTest(int firtInt, ...) {
    va_list itor;
    //va_start 使得 args 指向...的第一个参数的地址,例如在 varInputTest(1, 2, 3, 4); 的调用情况下,就是指向2
    va_start(itor, firtInt);
    …………
}

    这个宏需要两个参数,第一个是上面定义的 va_list, 第二个是可变参数列表之前的那个参数。

    作用就是使得 va_list 的变量指向可变参数列表的首地址。这才是一般意义上的对 va_list的初始化。

    2)实现

#define va_start ( ap, v ) ( ap = (va_list)&v + _INTSIZEOF(v) ) 
    很容易看懂,就是将ap指向v后的地址。

   3)注意:

   按照规范,va_start中的参数,一定要是最后一个参数,也就是...之前的那个参数,不然可能会有问题。尤其是windows和linux的函数参数入栈顺序不同,会有可移植性问题


3、va_arg

    1)用法:

void varInputTest(int firtInt, ...) {
    va_list itor;
    va_start(itor, firtInt);

    int current =  va_arg(itor, int);
    cout << current << endl;
    同样有两个参数,第一个是前面已经初始化好的 va_list,第二个是类型,比如这里可变参数列表的第一个参数是int类型,那么就传int。

    这个函数实现了类似于迭代器的功能,他的返回值是当前itor指向的 int类型值(类型是第二个参数所描述的),同时会移动 itor,使得其指向可变参数列表的下一个参数。

    2)实现:未研究,后面看了再补。


4、va_end

    1)用法:

va_end(itor);

    2)实现: 这个宏实际上是个空实现,更多是提高代码的可读性,同时方便后面的扩展。


5、缺少的....

    作为一个迭代器,缺少的最关键一环就是判定结尾,可惜这里是没法提供的。还是老老实实的结尾传个NULL吧。


四、一段简单的程序

#include <iostream>
#include <stdarg.h>

using namespace std;

void varInputTest(int unusedInt, ...) {
    va_list itor;
    //va_start 使得 args 指向...的第一个参数的地址,例如在 varInputTest(1, 2, 3, 4); 的调用情况下,就是指向2
    //这里第一个参数不用,不过也不好用占位符,毕竟还需要他的名称
    va_start(itor, unusedInt);

    int current = va_arg(itor, int);
    while ( current != -1 ) {
        cout << current << endl;
        current = va_arg(itor, int);
    }
    va_end(itor);
}

int main(int argc, char ** argv) {
    varInputTest(0, 1, 2, 3, 4, -1);
}

猜你喜欢

转载自blog.csdn.net/ronintao/article/details/10070485