c实现:
基本用法
c标准库提供一套可变参数列表使用方法,va_list / va_start / va_arg / va_end 。
demo:
double average ( int num, ... )
{
va_list arguments;
double sum = 0;
/* Initializing arguments to store all values after num */
va_start ( arguments, num );
/* Sum all the inputs; we still rely on the function caller to tell us how
* many there are */
for ( int x = 0; x < num; x++ )
{
sum += va_arg ( arguments, double );
}
va_end ( arguments ); // Cleans up the list
return sum / num;
}
va_start 接受一个 va_list 和 函数的入参的最后一个参数,va_start 返回的时候,就已经把入参的 ... 中的数据代理给 va_list了,其实是 va_list 是一个指针,指向了 ... 中的第一个参数。
va_arg 会使用指定的数据类型解析 va_list 中最前面的那个参数并返回,底层上是找到 va_list 指向的地址,然后用入参中的数据类型去解析这个地址里面的值并返回。这里第二个入参的数据类型很重要,这决定了如何解析 va_list 指向的地址内的值。比如如果是char,则va_list只读一个字节,然后va_list会向高地址移动一个字节以备下一次va_arg读取操作。如果是64位上的long,则一次读取8个字节。
va_end 用来释放va_list,底层上入参出栈操作,这里不用关心,只需要记住函数退出之前使用va_end 释放 va_list即可。
嵌套
某些函数接收的是 ... 作为入参,而某些函数接收的是 va_list 作为入参,接收 ... 作为入参的函数是无法被 wrapper 的,而 va_list 作为入参则可以通过传 va_list 实现wrapper。比如:
无法被wrapper的 g_object_set
void
g_object_set (gpointer _object,
const gchar *first_property_name,
...)
{
GObject *object = _object;
va_list var_args;
g_return_if_fail (G_IS_OBJECT (object));
va_start (var_args, first_property_name);
g_object_set_valist (object, first_property_name, var_args);
va_end (var_args);
}
可以被wrapper 的 g_object_set_valist
void
g_object_set_valist (GObject *object,
const gchar *first_property_name,
va_list var_args)
{
GObjectNotifyQueue *nqueue;
const gchar *name;
g_return_if_fail (G_IS_OBJECT (object));
g_object_ref (object);
nqueue = g_object_notify_queue_freeze (object, FALSE);
name = first_property_name;
while (name)
{
GValue value = G_VALUE_INIT;
GParamSpec *pspec;
gchar *error = NULL;
pspec = g_param_spec_pool_lookup (pspec_pool,
name,
G_OBJECT_TYPE (object),
TRUE);
if (!g_object_set_is_valid_property (object, pspec, name))
break;
G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
0, &error);
if (error)
{
g_warning ("%s: %s", G_STRFUNC, error);
g_free (error);
g_value_unset (&value);
break;
}
consider_issuing_property_deprecation_warning (pspec);
object_set_property (object, pspec, &value, nqueue);
g_value_unset (&value);
name = va_arg (var_args, gchar*);
}
g_object_notify_queue_thaw (object, nqueue);
g_object_unref (object);
}
再举个例子
printf的函数原型为:int printf(const char* fmt,...)
vprintf的函数原型为:int vprintf(char* fmt, va_list arg)
如果对printf做wrapper则不行,因为printf不接受va_list入参:
int myprintf(const char* fmt, ...)
{
va_list li;
va_start(li,fmt);
printf(fmt,li);
va_end(li);
}
如果对vprintf做wrapper则可以:
int myprintf(const char* fmt, ...)
{
va_list li;
va_start(li,fmt);
vprintf(fmt,li);
va_end(li);
}
c++实现:
c++有模板参数列表类来实现动态参数列表功能,不过限制是参数列表只能是相同类型。