L J Z
0 1

Lua unpack和require探究

table.unpack

  • 先思考一段代码:

    function printParam(a, b, c, d, e, f)
        print(a, b, c, d, e, f)
    end
    
    function Test()
        local a = {nil, 2, nil, 4, nil, 5}
        printParam(unpack(a))
    end
    
    Test()
    

    结果(以下结果均在lua5.1测试):

    nil 2 nil 4 nil 5
    

    好像不出所料,打印的就是我们table表里的所有参数,都满足我们的预期

  • 在变化一下呢:

    function printParam(a, b, c, d, e, f)
        print(a, b, c, d, e, f)
    end
    
    function Test()
        local a = {nil, 2, nil, 4, nil, nil}
        printParam(unpack(a))
    end
    
    Test()
    

    结果:

    nil nil nil nil nil nil
    

    有没有出乎意料呢?为什么打印的结果全是nil呢?

翻阅一下Lua官方文档


可以看到unpack其实是遍历list表,从下标0遍历到#table的长度。我们再看看#table的定义:

概括一下,一个table表里面,会被nil划分成多个边界,当table存在多个边界的时候,#table可以返回它的任何边界的值(取决于表的内部表示的细节,而这又取决于表的填充方式及其非数字键的内存地址)。

  • 这时候结合去理解一下用例二
    我们用例二的table表是{nil, 2, nil, 4, nil, nil},这个时候就有多个边界为(0,2,4),而返回的边界这时候是0。注意:你可能在这个过程中会发现一些规律,但是Lua告诉我们这不是一个合理的规律,我们要做的就是避免table表中的nil值。

创建我们自己的unpack去规避问题

table.pack2 = function(...)
    return { n = select('#', ...), ... }
end

table.unpack2 = function(t)
    return table.unpack(t, 1, t.n)
end

通过存储n的长度,避免#table的读取错误,从而规避unpack的问题

Lua Require

先来了解一下require的处理流程:

  • require(modelname)
    require(在lua中它是ll_require函数)的查找顺序如下:

    • 首先在package.loaded查找modelname,如果该模块已经存在,就直接返回它的值

    • 在package.preload查找modelname, 如果preload存在,那么就把它作为loader,调用loader(L)

    • 根据package.path的模式查找lua库modelname,这个库是通过module函数定义的,对于顶层的lua库,文件名和库名是一 样的而且不需要调用显式地在lua文件中调用module函数(在ll_require函数中可以看到处理方式),也就是说lua会根据lua文件直接完 成一个loader的初始化过程。

    • 根据package.cpath查找c库,这个库是符合lua的一些规范的(export具有一定特征的函数接口),lua先已动态的方式加载该c库,然后在库中查找并调用相应名字的接口,例如:luaopen_hello_world

    • 已第一个"."为分割,将模块名划分为:(main, sub)的形式,根据package.cpath查找main,如果存在,就加载该库并查询相应的接口:luaopen_main_sub,例如:先查找 hello库,并查询luaopen_hello_world接口

    • 得到loder后,用modname作为唯一的参数调用该loader函数。当然参数是通过lua的栈传递的,所以loader的原型必须符合lua的规范:int LUA_FUNC(lua_State *L)

    • 最后注意:ll_require会将这个loader的返回值符给package.loaded[modelname],如果loader不返回值同时 package.loaded[modelname]不存在时, ll_require就会把package.loaded[modelname]设为true。最后ll_reuqire把package.loaded [modelname]返回给调用者。uqire把package.loaded [modelname]返回给调用者。

  • 了解了加载的流程,这时候当我们require过了一个模块后,如果这个模块被修改了(运行时等情况),我们是不能通过调用require来使得这个模块重新加载的。有一个通用的方法

    package.loaded[file] = nil
    package.preload[file] = nil
    

    保险起见我们一般都是把preload的值也置空,避免出现遗漏的情况。

posted @ 2022-10-30 22:57  小小钊  阅读(240)  评论(0编辑  收藏  举报