Lua的require小结

摘要:
v) 结束时可以看到预加载模块的名称。Lua从哪里加载模块?如果我们想修改Lua加载模块的路径,需要(“mypackage”)fork,并发现自定义模块返回的值为true。模块的返回值(如果有且不是nil)作为值插入package.loaded中。实际上,在内部调用loadfile接口来加载模块。

在游戏开发中会经常使用到lua作为游戏逻辑层的脚本语言,各种优势就不说了,虽然平时用的比较多,但对lua语言本身和内部的一些实现并不是很了解,让我们先从lua的require入手来一探require的各种用法吧。

require其实类似与C/C++中的#include,就是加载一个指定名称的模块进来,该模块可以来自于lua,也可能来自于C/C++,在lua虚拟机启动时,默认会给我们预先加载好一些模块,保存在package.loaded中,我们可以实际打印一波看看:

for k, v in pairs(package.loaded) do
	print(k, v)
end

Lua的require小结第1张

可以看到预先加载好的模块名称,一目了然。那么,lua又是从哪些地方去加载模块呢?加载模块又有什么规则呢?这个就是由package.path指定,同样可以实际打印一波看看:

Lua的require小结第2张

如果我们希望修改lua加载模块的路径,只要修改这个package.path就可以了。

让我们回到前面打印的package.loaded结果,我们发现这个table的value都是table,这不禁让人好奇:require的返回值是什么呢?我们可以自己写一个简单的自定义模块去验证下:

--mypackage.lua
print("hello world")

然后,执行:

require("mypackage")
for k, v in pairs(package.loaded) do
	print(k, v)
end

Lua的require小结第3张

诶,发现自定义模块返回的值是true。这是为什么呢?明明我们的代码里没有任何一句return语句。难道是在没有写返回值的情况下,默认给我们返回true了?那既然如此,手动显式加一句return试试:

--mypackage.lua
print("hello world")
return nil

然后require,我们发现结果还是一样,返回值为true:

Lua的require小结第4张

其实,lua之所以这么做,是为了避免重复加载同一个模块,每加载一个模块,就将模块的name作为key,模块的返回值(如果有且不为nil)作为value插入到package.loaded中去。这样下次再去加载这个模块时,就无需加载再去执行该模块的代码,直接返回package.loaded对应key的value即可。怎么验证呢?我们可以尝试require一个模块两次试试:

require("mypackage")
require("mypackage")

Lua的require小结第5张

注意到,hello world只被打印了一次,说明第二次require的时候并没有执行mypackage中的代码。require内部实际上是调用了loadfile接口来进行模块加载,loadfile的返回值是一个函数,执行该函数,相当于执行该模块的代码:

f = loadfile("D:/lua/mypackage.lua")
f()

Lua的require小结第6张

那么,有没有办法让重复require时都去执行模块的代码呢?答案是显而易见的,只要将package.loaded中对应的key删掉就可以了:

require("mypackage")
package.loaded.mypackage = nil
require("mypackage")

Lua的require小结第7张

有意思的是,如果我们的模块返回值为false,或者我们设置package.loaded.mypackage = false时,无论require多少次,都会触发模块的加载执行。不过根据我们之前的验证,这也是符合情理的hhh。说到这里,其实我们就可以自己写一个简单的require了:

function require_ex(module)
    if package.loaded[module] then
        return package.loaded[module]
    end

    for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', module)
        local fp = loadfile(path)
        if fp then
            local ret = fp()
            if ret ~= nil then
                package.loaded[module] = ret
            else
                package.loaded[module] = true
            end
            return package.loaded[module]
        end
    end
end

有时候,我们希望require进来的模块是不允许定义全局变量的,因为全局变量会污染我们整个环境,并可能造成意想不到的后果,在lua 5.1,我们可以使用setfenv函数来设置函数环境,而在lua 5.2以上版本,则可以通过修改env参数来解决,loadfile的第三个参数就是函数环境:

    local env = {}
    setmetatable(env, {__index = _G, __newindex = function(t, k, v) print("forbidden global var ", k) end})
    local fp = loadfile(path, nil, env)

如果lua在package.path中找不到对应的lua模块,那么接下来它会尝试从C++模块中加载,类似地,C++路径是由package.cpath指定的:

Lua的require小结第8张

针对dll,require内部是使用package.loadlib方法实现的,它接受两个参数,一是模块的路径,二是给lua调用的函数名称(lua_openxxx)。其他的就基本和前面加载lua模块一致了,完整的require_ex代码如下:

function require_ex(module)
    if package.loaded[module] then
        return package.loaded[module]
    end

    for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', module)
        local env = {}
        setmetatable(env, {__index = _G, __newindex = function(t, k, v) print("forbidden global var ", k) end})
        local fp = loadfile(path, nil, env)
        if fp then
            local ret = fp()
            if ret ~= nil then
                package.loaded[module] = ret
            else
                package.loaded[module] = true
            end
            return package.loaded[module]
        end
    end

    for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', module)
        local fp = package.loadlib(path, "luaopen_" .. module)
        if fp then
            local ret = fp()
            if ret ~= nil then
                package.loaded[module] = ret
            else
                package.loaded[module] = true
            end
            return package.loaded[module]
        end
    end
end

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

Lua的require小结第9张

免责声明:文章转载自《Lua的require小结》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇(转)Openlayers 2.X加载天地图[DevExpress使用随笔]之预览repx报表(转)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

geotrellis使用(四十二)将 Shp 文件转为 GeoJson

前言 一个多月没有写博客了,今天尝试着动笔写点。 原因很多,最重要的原因是我转行了。是的,我离开了开发岗位,走向了开发的天敌-产品经理。虽然名义上是产品经理,但是干的事情也很杂,除了不写代码,其他的都干,经常还要加个小班,所以就没那么多时间研究技术上的东西,机械键盘上已经落下了一层薄薄的灰尘。但是自己确实又爱码农这一行,上班看着同事畅快的敲着代码,心里就有...

第五节:STM32输入捕获(用CubeMX学习STM32)

输入捕获学习 《用CubeMX学习STM32》 注释 点击上面蓝字进入完整专栏,这个系列所有文章都会整合到这个专栏 5、STM32定时器输入捕获 前言: STM32定时器输入捕获简介 STM32的输入捕获可以用于捕获脉宽, 测量时间 . 例如超声波测距模块就是需要用输入捕获功能, 通过测量输入脉冲的高电平脉宽 , 从而计算出测量物体的...

CSS总结div中的内容垂直居中的五种方法

一、行高(line-height)法 如果要垂直居中的只有一行或几个文字,那它的制作最为简单,只要让文字的行高和容器的高度相同即可,比如: p { height:30px; line-height:30px; width:100px; overflow:hidden; } 这段代码可以达到让文字在段落中垂直居中的效果。 二、内边距(padding)法 另一...

从零开始编写自己的C#框架(12)——T4模板在逻辑层中的应用(一)(附源码)

对于T4模板很多朋友都不太熟悉,它在项目开发中,会帮我们减轻很大的工作量,提升我们的开发效率,减少出错概率。所以学好T4模板的应用,对于开发人员来说是非常重要的。   园子里对于T4模板的介绍与资料已经太多了,所以在这里我就不再详细讲述基础知识了,只是说说T4模板在本框架中的具体应用与实践。   一、创建逻辑层项目         二、添加引用      ...

将子分支代码merge到主分支master分支

1.首先切换到子分支; git checkout develop2.使用git pull 把分支代码pull下来; git pull3.切换到主分支; git checkout master4.把分支的代码merge到主分支; git merge develop5.git push推上去ok完成,现在 你自己分支的代码就合并到主分支上了。 git push...

SQL实现类似于自动刷新数据的功能

有时需要在SQL中,定时刷新某张表,比如说是要定时查询某张表的行数,通常做法就是手动的按F5去执行来刷新数据。但是如果这个定时查询历时较长,10分钟,或半小时,手动的话肯定是要崩溃了。貌似SQL没有像应用程序自动刷新的功能,可能是我还不知道有这样的功能,于是就写了一个类似自动刷新数据的脚本,需要手动中止程序的运行,当然也可以写个条件,自动结束语句的执行。...