可变参数函式
在C中函式int printf(const char *format, ...)
就是用来输出讯息到画面上的,在这个函式的参数中有个比较特别的参数...
,它就是C语言用来表达可变参数,意即参数的数量不定。
#include<stdio.h>#include<stdarg.h>void printList(int head, ...){ va_list args; va_start(args, head); for (int i = head; i != -1; i = va_arg(args, int)){ printf("%d\n", i); } va_end(args);}int main(void){ printList(4, 5, 6, 7, 8, 9, -1);}
输出结果
456789
上面的程式码有几个我们需要关注的点。
va_list
: 型态是指标,可能是void*
或char*
,依据不同编译器,可能会有不同结果。va_start
: 是一个marco,样式void va_start(va_list arg_ptr, last_arg)
。va_arg
: 是一个marco,样式type va_arg(va_list arg_ptr, type)
。va_end
: 是一个marco,样式void va_end(va_list arg_ptr)
。以上这些都是包含在C的<stdarg.h>
中。
在函式的参数以...
代替一般参数,之后再调用函式时就可以填入任意数量的参数。
arg_ptr
是一个变数,用来指向最后一个固定的参数last_arg
(即範例中的head
)。va_start
用来抓取最后一位固定的参数,使arg_ptr
指向last_arg
的下一个参数,即第一个可变参数。va_arg
则是用来抓取那些任意数量的参数,type
指的是传入参数的资料型态,由于取值的方法跟地址偏移量有关,所以需要知道资料类型。va_end
用来表示终止检索那些传入任意参数,实际操作只是使arg_ptr
指向0以免意外发生。
这里有几个重点
宣告的固定参数在调用时不能少之后任意数量的参数型态都要一样(即...
所包含的参数型态都要一样)macrova_arg
抓参数时,没法辨认有多少个参数,不知何时终止,需要程式设计师自己处理。参数的型态有许多限制,大多数都不能用,或者会被强制转成int
unsigned int
double
,下面这些不能使用。char、signed char、unsigned char、short、unsigned short、signed short、floatC99标準添加了va_copy
这里提一个我觉得比较重要的一点,我们可能会有个疑惑,为啥C设计时要我们设计师自己处理参数的数量,不能在传入时顺便统计一下。
问题点就是va_arg
这个marco取得参数的设计,由于函式在堆叠区展开时参数的地址是连续的,va_arg
抓取参数时是由指标移动偏移量取得的,这样就没法得知我们传入了多少参数。
我们较常处理这问题的方法:
传入一个数字,告知函式有多少个参数需要处理传入一个终止符,在检索到终止符时自动停止在C的<stdarg.h>
中,还有个marco_INTSIZEOF(n)
跟上述的那些marco的运作和记忆体对齐有关,有兴趣了解的可以去翻翻源码。
可变参数marco
我们在定义marco也可以像函式使用...
,表达可变参数。
#include<stdio.h>// __VA_ARGS__是原本C用来表达可变参数的替代词#define feedback1(...) ( \ printf(__VA_ARGS__) \ )// 如果我们不想用C定义的__VA_ARGS__// 我们可以自己定义一个名称,格式如下#define feedback2(args...) ( \ printf(args) \ )// 下面的例子相比上面多个##// 如果我们没有传入参数给args// 那么按照define的功能,文字替代// 文字替代后会产生printf(format, )// 这样因为有','所以编译会有问题// ##有个功能就是在没有参数传入的情况下// 消掉多余的',',使得结果变成printf(format)#define my_print(format, args...) ( \ printf(format, ##args) \ )int main(void) { feedback1("feedback1: I'm hungry.\n"); feedback2("feedback2: I'm hungry.\n"); my_print("my_print: I'm hungry.\n"); my_print("my_print: num1 = %d, num2 = %d\n", 45, 54); return 0;}
输出结果
feedback1: I'm hungry.feedback2: I'm hungry.my_print: I'm hungry.my_print: num1 = 45, num2 = 54
feedback1
中__VA_AGRS__
是C自有的名称,编译器会用来换成可变参数。
但我们也可以自己定义名称,格式如feedback2
的args...
。
当没有额外参数传入可能会产生多余的,
,造成格式编译不过。##
可以在没有参数的情况下,消掉多余的,
。
marco的可变参数,没办法单独读取参数,意即我们没办法像函式va_arg
一次次抓取变数,所以相对于函式有时候并没法简单值观的实现某些特定方法。