目录
2.3 强制函数require 加载同一模块两次,可以先将模块从package.loaded中删除
3.1 创建一个表,然后把所有需要导出的函数放入,最后返回这个表
一个模块就是一些 Lua语言或者C语言编写的代码(比如标准库math等),通过函数require加载,然后创建和返回一个表,表中的内容就是模块导出的东西(函数,常量等)
local m = require "math"
print(m.sin(3.14))
独立解释器会使用和下面代码等价的方式提前加载所有的标准库(简单说,就是独立解释器会预加载所有标准库)
math = require "math"
string = require "string"
...
一. 模块的使用
1.1 我们可以调用模块中的方法
local mod = require "mod"
mod.foo()
1.2 可以给模块设置一个局部名称
local m = require "mod"
m.foo()
1.3 可以为函数提供不同名称
local m = require "mod"
local f = m.foo
f()
1.4 只引入特定函数
local f = require "mod".foo
f()
二. 函数 require
2.1 当函数的参数只有一个字符串是,括号可以省略
local m = require("math")
-- 或者
local modname = 'math'
local m = require(modname)
2.2 函数require加载模块流程
为了最后加载模块,函数require带着两个参数调用加载函数(加载函数见 流程图):模块名和加载函数所在文件名,如果加载函数有返回值,那么函数require会返回该值,然后保存在package.loaded中,以便将来加载同一个模块是返回相同值,如果加载函数没有返回值并pack.loaded[@rep{modname}]为空,则函数require会假设模块返回true
2.3 强制函数require 加载同一模块两次,可以先将模块从package.loaded中删除
package.loaded.modname = nil
2.4 给待加载的模块添加参数
由于函数require 会避免重复调用模块,所以不能直接给待加载的模块传参,这样,不同参数的同名模块之间会产生冲突
-- 错误的代码
local math = require("math","degree")
-- 可以使用一个显式的函数设置参数
local mod = require 'mod'
mod.init(0,0) -- mod中添加一个init函数
-- 返回的是函数本身
local mod = require "mod".init(0,0)
2.5 模块重命名
通常,我们通过模块本身的名称来使用,例如,要使用math 数字库,则require("math")即可,但有时候还需要给模块改名来避免命名冲突。
例如,出于测试的目的加载同一模块不同版本
-- mod.lua文件
local mod = require "mod" -- 加载mod模块
如果需要加载mod模块的不同版本,通常只需要修改.lua文件的文件名,即
-- mod.lua 改名为 mod1_0.lua
local mod1_0 = require "mod" -- 我还是调用package.loaded 中mod模块,但是我想require返回的是 mod1_0.lua文件的结果
但此时修改了文件名的话,require 函数加载"mod"模块时 会找不到mod文件,因为我们使用者无法修改 C标准库的Luaopen*_ 函数名(详情见 requrie加载流程图)。
那要怎么加载 mod模块的 mod1_0版本呢????
函数require 运用了一个连字符的技巧:如果一个模块中包含连字符,那么函数require就会用连字符之前的内容来创建 luaopen_*的函数名称
例如:require "mod-v3.4" , 那么函数require会认为该函数的加载函数是luaopen_mod而不是 luaopen_mod-v3.4
local mod = require "mod" -- 会在package.loaded 中调用 mod模块,然后 loadfile 寻找 mod.lua,并通过require返回
local mod-v1 = require "mod-v1" -- 会在package.loaded 中调用 mod模块,然后 loadfile 寻找 mod-v1.lua,并通过require返回
上面就实现了 同时调用 package.loaded的 mod模块中的不同版本功能
2.6 搜索路径(详情见requrie加载流程图)
local mod = require "mod"
2.6.1 搜索路径是什么?
当我们调用package.loaded中的 mod模块时,package.loaded会检查 mod 有没有加载过,没有加载过的话,require会去找 mod.lua文件,既然要寻找,那么搜索路径是什么呢?
函数require 使用的路径是一组模板。例如
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua -- 这组模板是自定义的,require只管分号和问号,其它的部分自定义
其中 ?会被模块名(也就是require函数的参数)替换,分号则是分隔符,将几组模板分隔开,如果该分隔符内不存在对应文件,则尝试下一组模板
像 require "sql"的搜索路径分别尝试以下几组路径
sql
sql.lua
c:\window\sql
/user/local/lua/sql/sql.lua
2.6.2 搜索路径的相关函数
变量/函数 | 定义 |
package.path | 此变量的当前值为 函数require搜索文件的路径 |
package.cpath | 此变量的当前值为 C标准库的搜索文件路径 |
package.searchpath(模块名,路径) | 实现了搜索库的所有规则,返回第一个存在的文件名,或者 返回nil 加 所有文件无法打开的错误信息 |
package.path
当pack模块初始化时,会把 package.path设置成环境变量 LUA_PATH_5_3的值,若该环境变量不存在,则尝试设置成LUA_PATH的值,若还是没有,则lua语言会使用一个编译时使用的默认路径。
使用环境变量时,Lua语言会将其中所有的";;"替换成默认路径
例如,将 LUA_PATH_5_3设为"mydir/?.lua;;", 那么最终路径会是模板
"mydir/?.lua" + 默认路径
package.cpath
搜索C标准库路径的逻辑同上,只不过C标准库的路径来自变量package.cpath, 该变量的初始值也来自LUA_PATH_5_3 或 LUA_PATH
搜索C标准库路径的标准模板为
-- 在 POSIX系统中,例如
./?.so;/user/local/lib/lua/5.2/?.so
-- 在window系统中
.\?.dll;C:\Porgram Files\Lua502\dll\?.dll
package.searchpath(模块,路径)
path = ".\\?.dll;C:\\Program Files\\Lua502\\dll\\?.dll"
print(package.searchpath(x,path))
-- nil
-- no file './X.dll'
-- no file 'C:/Program Files\Lua502\dll\X.dll'
需求一:实现一个和函数 package.searchpath类似的函数
function search(modName, path)
modName = string.gsub(modName, "%.", "/")
local msg = {}
for c in string.gmatch(path,"[^;]+") do
local fname = string.gsub(c, "?", modName)
local f = io.open(fname)
if f then
f:close()
return fname
else
msg[#msg + 1] = string.format("\ntno file '%s'", fname);
end
end
return nil, table.concat(msg) -- 没找到
end
2.7 搜索器
搜索Lua文件和C标准库的方式只是搜索器(searcher)的两种实例
一个搜索器是以模块名为参数,以对应模块的加载器或nil(没找到搜索器) 为返回值的简单函数
数组 package.searchers列出了函数require使用的所有搜索器。在寻找一个模块时,函数require传入模块名并调用列表中每一个搜索器直到找到其中的一个指定模块的加载器。如果没找到,函数require 抛出一个异常
3. 如何编写模块
3.1 创建一个表,然后把所有需要导出的函数放入,最后返回这个表
local M = {} -- 模块
创建 一个新的复数
local function new(r, i)
return {r = r, i = i}
end
M.new = new -- 把 new 加到模块中
-- constant 'i'
M.i = new(0, 1)
function M.add (cl, c2)
return new( cl.r + c2.r, cl.i + c2.i)
end
function M.sub (cl, c2)
return new(cl.r - c2.r, cl.i - c2.i)
end
function M.mul(c1, c2)
return new(cl.r * c2.r - c1.i*c2.i, cl.r* c2.i + c1.i*c2.r)
end
local function inv (c)
local n = c.r^2 + c.i^2
return new(c. n, -c.i/n)
end
function M.div (cl, c2)
return M.mul(cl, inv(c2) )
end
function M.tostring(c)
return string.format("(%g,%g)",c.r,c.i)
end
return M
3.2 把所有函数定义为局部变量,然后在最后构造返回的表
local function new(r,i) return {r=r,i=i}end
local i = complex.new(0,1)
-- 省略跟之前一样的函数
return{
new = new,
i = i,
add = add,
sub = sub,
mul = mul,
div = div,
tostring = tostring,
}
这种方式的优点在于,无须在每一个标识符前增加前缀 M. 或类似的东西 通过显式的导出表,我们能够以与在模块中相同的方式定义和使用导出和内部函数 这种方式的缺点在于,导出表位于模块最后而不是最前面(把前面的话当作简略文档的话更有用),而且由于必须把每个名字都写两遍,所以导出表有点冗余(这 缺点其实可能会变成优点,因为这允许函数在模块内和模块外具有不同的名称,不过程序很少会用到)