Lua的require小结
在游戏开发中会经常使用到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又是从哪些地方去加载模块呢?加载模块又有什么规则呢?这个就是由package.path指定,同样可以实际打印一波看看:

如果我们希望修改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

诶,发现自定义模块返回的值是true。这是为什么呢?明明我们的代码里没有任何一句return语句。难道是在没有写返回值的情况下,默认给我们返回true了?那既然如此,手动显式加一句return试试:
--mypackage.lua
print("hello world")
return nil
然后require,我们发现结果还是一样,返回值为true:

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

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

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

有意思的是,如果我们的模块返回值为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指定的:

针对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小结的更多相关文章
- Lua的require和module小结
Lua的require和module小结 module特性是lua5.1中新增的,用于设置Lua文件自己的模块,最常用的方式是module(name,package.seeall),有时候lua文件 ...
- Lua 字符串函数小结
1.求字符串长度 string.len(str) 2.大小写转换 string.upper(str) string.lower(str) 3.字符串查找(非全局) --func_string.lua ...
- Lua脚本认知小结
0.前言 Lua是一种脚本语言,笔者在学习cocos2dx的时候认识了这个脚本语言. 据个人了解的脚本语言最大的优势是无需编译,使用其内核可以使其跨平台运行. JavaScript,Python,Pe ...
- Lua中的require
lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关 ...
- Lua Require函数
转自:http://www.cppblog.com/cslover/archive/2013/12/21/204934.html Lua提供高级的require函数来加载运行库.粗略的说require ...
- 【转载】lua中的require机制
[转载自]http://blog.chinaunix.net/uid-552961-id-2736410.html lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然 ...
- Lua中的require(转)
lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关 ...
- lua中的require机制
lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关的数据 ...
- Lua require 相对路径
lua require 加载方式与我们现在熟知的路径系统不太一样,想要知道lua require 方法的工作原理也很简单 随便写一个错误的require 代码即可: 1 require("l ...
随机推荐
- zabbix的搭建及操作(1)server-client架构
实验环境 Server端 Centos7:192.168.10.10 server.zabbix.com 可连外网 Agent 端 Centos7:192.168.10.20 ...
- FLstudio 系列教程(六):如何安装FL studio
FL Studio简称FL,全称:Fruity Loops Studio,因此国人习惯叫它"水果".目前版本是FL Studio20,它让你的计算机就像是全功能的录音室,大混音盘, ...
- 这些Stream流的常用方法你得记住,步骤简单不麻烦!
forEach遍历 /* forEach:该方法接收一个Consumer接口函数,将每一个流元素交给该函数处理 简单记: forEach方法:用来遍历流中的数据 是一个终结方法,遍历之后就不能继续调用 ...
- 蓝桥杯——测试次数·摔手机(2018JavaB组第4题,17分)
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机. 各大厂商也就纷纷推出各种耐摔型手机.x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通. x ...
- Linux 系统故障排查,这里看过来
来源:https://www.jianshu.com/p/0bbac570fa4c 导读 有时候会遇到一些疑难杂症,并且监控插件并不能一眼立马发现问题的根源.这时候就需要登录服务器进一步深入分析问题的 ...
- TIOBE 11月指数:C语言居首,稳居宝座,Python直逼第二!
官方网址:https://www.tiobe.com/tiobe-index/ 这是自近20年前TIOBE指数开始以来,Java和C第一次不再占据前两位.C仍然是第一位的,但是现在第二个位置是 ...
- dubbo起停之服务暴露
由上一节可知带上dubbo@Service注解的对象,在注册成为bean之后会进一步注册一个ServiceBean,服务暴露便是在这里 public void afterPropertiesSet() ...
- java中的强引用(Strong reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)
之前在看深入理解Java虚拟机一书中第一次接触相关名词,但是并不理解,只知道Object obj = new Object()类似这种操作的时候,obj就是强引用.强引用不会被gc回收直到gc roo ...
- SQL优化之SELECT COUNT(*)
前言 SQL优化之SQL 进阶技巧(上) SQL优化之SQL 进阶技巧(下)中提到使用以下 sql 会导致慢查询 SELECT COUNT(*) FROM SomeTable SELECT COUNT ...
- day3(Vue组件)
1.组件定义 1.定义组件并引用 2.父组件向子组件传值 3.子组件向父组件传值 # 组件间传值:vuex (https://www.cnblogs.com/xiaonq/p/9697921.html ...