【参考】:
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);
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); }