0
点赞
收藏
分享

微信扫一扫

从cocos2d-x中学习c++(一):可变参数 ...

荷一居茶生活 2022-02-06 阅读 98

在cocos2d-x中的Sequence是一种特殊的动作,它可以表示一个动作序列。创建该动作通常使用这样的代码:

Action* sequence = Sequence::create(acttion1, action2, action3/*, action4, action5...*/, nullptr);

其中括号里的参数个数事实上是不限制的,可以是3个,也可以有更多。

那么这是如何实现的?

显然不应该是用重载函数的方式,不会有人想把1~9999个参数的函数都写一遍。
通过查看cocos2d-x中该函数的源码可以发现,该函数的参数中有这样的参数:

    static Sequence* create(FiniteTimeAction *action1, ...) ;

这个省略号…就是实现的关键,这个函数就可以支持写不限个数的参数。

那么函数内部要如何使用那若干个参数呢?

函数的实参列表中并不包含那若干个参数,只是有一个…。
在调用该函数的时候,那些参数真的传进来吗?如果进来了,又被保存在哪里呢?
调用函数是一个入栈的过程,函数的参数也会依次入栈,包括那些被…涵盖的参数。
所以只需要知道…所代表的若干参数的首地址,就可以读到这些参数,而且…参数的使用有要求,其后不能再跟其它形参了,所以获取参数首地址后就能够不多不少地读取所有参数数据了。
也就是说参数是进来了,但是栈中的参数数据并不包含其数据类型,而函数形参也没有说明进来了哪些类型的数据,为了能够正确读取这些数据,只能靠程序员说明是什么类型了。
所以具体会有这些步骤:

  1. 指定…所覆盖的实参首地址,并以此获取所有实参的数据;
  2. 依次说明参数类型,依次按照该数据类型读取数据;
  3. 读取完毕,释放…所覆盖的实参内存;

具体实现方法

步骤1通过va_start宏实现,需要两个参数,第一个参数是用来指向…所覆盖参数数据的指针,通常是一个va_list类型的变量(其实就是一个char*),第二个参数是…左边第一个参数,相当于提供了…所覆盖参数的首地址。
步骤2通过va_arg宏实现,也需要两个参数,第一个参数是上面说的指向参数数据的指针,第二个是要取出参数的类型。有一个返回值,即当前读取出的一个参数。一次只能读一个参数,要是想继续读就要继续调用该宏。
步骤3通过va_end实现,传入指向参数数据的指针即可。

这里给出cocos2d-x中的实现代码:

Sequence* Sequence::create(FiniteTimeAction *action1, ...)
{
    va_list params;
    va_start(params, action1);//步骤1

    Sequence *ret = Sequence::createWithVariableList(action1, params);//步骤2

    va_end(params);//步骤3
    
    return ret;
}

/************************************步骤2详细************************************/
Sequence* Sequence::createWithVariableList(FiniteTimeAction *action1, va_list args)
{
    FiniteTimeAction *now;
    FiniteTimeAction *prev = action1;
    bool bOneAction = true;

    while (action1)
    {
        now = va_arg(args, FiniteTimeAction*);//指定数据类型为 FiniteTimeAction*
        if (now)//如果读到了
        {
            prev = createWithTwoActions(prev, now);
            bOneAction = false;
        }
        else//如果读完了
        {
            // If only one action is added to Sequence, make up a Sequence by adding a simplest finite time action.
            if (bOneAction)
            {
                prev = createWithTwoActions(prev, ExtraAction::create());
            }
            break;
        }
    }
    
    return ((Sequence*)prev);
}

几个小问题

  • 上面说的va_start需要提供…左边第一个参数,是否是不能有纯…作为参数的函数,比如像这样的:void fun(...);但是这样写编译是通过的,那va_start第2个参数要写什么呢?如何才能正确获取参数数据呢?这种情况参数的首地址应该就是…所覆盖参数首地址,但这该如何说明呢?
  • 如果只看上面的代码来理解va_arg的用法可能会产生偏差。我一开始就以为如果参数读取完的话就会返回null(其实这种想法本身就不合理)。后来才知道原来va_arg本身不能告诉你参数是不是读完了,所以需要手动告知有几个参数。这有两个办法,一个是添加加一个参数用来说明…中参数的数量。另一个我觉得更好一些的办法是事先约定一个结束标志,然后在调用该函数时,一定以该结束标志作为最后一个实参。这样当va_arg读取到结束标志时,就可以知道读取结束了。cocos2d-x就是用的这种办法,在使用 Sequence::create()方法时需要最后一个参数必须为nullptr。

总结

…说实话使用起来感觉非常原始和不安全,还是要慎重使用。

笔者许多理解可能不当或者错误,欢迎批评指正。

举报

相关推荐

0 条评论