Lua_模块与包_加载机制_C包_脚本卸载_大G表(13)

目录


码云代码链接
https://gitee.com/wenwenc9/lua_pro.git

一、require

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下:

module.lua

module = {}

module.constant = '这是一个常量'

function module.func1()
    io.write('这是一个公有函数')
end

local function func2() 
    io.write('这是一个私有函数')
end

function module.func3()
    func2()
end

return module

一、require函数

  • require 会搜索目录加载文件
  • require 会判断是否文件已经加载避免重复加载同一文件

由于上述特征,require在 Lua 中是加载库的更好的函数。

require 使用的路径和普通我们看到的路径还有些区别,我们一般见到的路径都是一
个目录列表。require 的路径是一个模式列表,每一个模式指明一种由虚文件名(require
的参数)转成实文件名的方法。更明确地说,每一个模式是一个包含可选的问号的文件
名。匹配的时候 Lua 会首先将问号用虚文件名替换,然后看是否有这样的文件存在。如
果不存在继续用同样的方法用第二个模式匹配。例如,路径如下:

?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua 

调用 require "lili"时会试着打开这些文件:

lili 
lili.lua 
c:\windows\lili 
/usr/local/lua/lili/lili.lua 

require 关注的问题只有分号(模式之间的分隔符)和问号,其他的信息(目录分隔
符,文件扩展名)在路径中定义。
为了确定路径,Lua 首先检查全局变量 LUA_PATH 是否为一个字符串,如果是则认
为这个串就是路径;否则 require 检查环境变量 LUA_PATH 的值,如果两个都失败 require使用固定的路径(典型的"?;?.lua")

require 的另一个功能是避免重复加载同一个文件两次。Lua 保留一张所有已经加载
的文件的列表(使用 table 保存)。如果一个加载的文件在表中存在 require 简单的返回;表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名 require同一个文件两次,将会加载两次该文件。比如 require "foo"和 require "foo.lua",路径为"?;?.lua"将会加载 foo.lua 两次。我们也可以通过全局变量_LOADED 访问文件名列表,这样我们就可以判断文件是否被加载过;同样我们也可以使用一点小技巧让 require 加载一个文件两次。比如,require "foo"之后_LOADED[“foo”]将不为 nil,我们可以将其赋值为 nil,require "foo.lua"将会再次加载该文件。

一个路径中的模式也可以不包含问号而只是一个固定的路径,比如:

?;?.lua;/usr/local/default.lua 

这种情况下,require 没有匹配的时候就会使用这个固定的文件(当然这个固定的路
径必须放在模式列表的最后才有意义)。在 require 运行一个 chunk 以前,它定义了一个全局变量_REQUIREDNAME 用来保存被 required 的虚文件的文件名。我们可以通过使用这个技巧扩展 require 的功能。举个极端的例子,我们可以把路径设为"/usr/local/lua/newrequire.lua",这样以后每次调用 require 都会运行 newrequire.lua,这种情况下可以通过使用_REQUIREDNAME 的值去实际加载 required 的文件。

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:

require("<模块名>")

或者

require "<模块名>"

执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3()

执行结果

这是一个常量
这是一个私有函数!

可以给加载的模块定义一个变量名,方便调用

二、加载机制

package.path = package.path..";D:/lua-wenwenc9/lua_pro/ws3c/12_模块与包/?.lua"

对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

文件路径以 “;” 号分隔,最后的 2 个 “;;” 表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。

source ~/.profile

这时假设 package.path 的值是:

/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

那么调用 require(“module”) 时就会尝试打开以下文件目录去搜索目标。

/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua

如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。
搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。
搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

三、C包

Lua和C是很容易结合的,使用C为Lua写包。
与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
local path = “/usr/local/lua/lib/libluasocket.so”
local f = loadlib(path, “luaopen_socket”)

loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。
如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- 真正打开库

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。
将stub文件所在的目录加入到LUA_PATH,这样设定后就可以使用require函数加载C库了。

四、脚本卸载

require('Test')
print(package.loaded['Test'])
require('Test') -- 加载一次不会加载二次

-- 卸载
package.loaded['Test'] = nil

五、大G表

可以打印此脚本中,所有的系统变量,自己定义变量,参数的都在大G表中存储,
面向对象创建的时候会用到

a = {}
b = 2
function fun()
    print('hello')
end

-- 打印大G表
for k,v in pairs(_G) do
    print(k,v)
end

a	table: 00C29018
string	table: 00C28F78
xpcall	function: 00C272A8
b	2
package	table: 00C26760
tostring	function: 00C27008
print	function: 00C26EA8
os	table: 00C28F50
unpack	function: 00C272C8
require	function: 00C27E00
getfenv	function: 00C26D48
setmetatable	function: 00C26CE8
next	function: 00C26B48
assert	function: 00C26E28
tonumber	function: 00C26F68
io	table: 00C293B0
rawequal	function: 00C26C28
collectgarbage	function: 00C26B68
arg	table: 00C29400
getmetatable	function: 00C26DE8
module	function: 00C28080
fun	function: 00C2B4C0
rawset	function: 00C26DC8
math	table: 00C29388
debug	table: 00C29108
pcall	function: 00C26F08
table	table: 00C267D8
newproxy	function: 00C20520
type	function: 00C26F48
coroutine	table: 00C26738
_G	table: 00C22FB0
select	function: 00C26C08
gcinfo	function: 00C26E88
pairs	function: 00C21348
rawget	function: 00C26C48
loadstring	function: 00C26DA8
ipairs	function: 00C21458
_VERSION	Lua 5.1
dofile	function: 00C26B88
setfenv	function: 00C26C88
load	function: 00C26CC8
error	function: 00C26D08
loadfile	function: 00C26E08
上一篇:秒杀代码,非lua脚本。


下一篇:笔记 - Lua 程序设计(第 2 版) Ch01~03 开始、类型与值、表达式