模块

  • 一些代码通过require加载后,返回到一个table
    • 将加载的内容到package.loaded
  • 标准库都是模块,Lua会进行提前加载

    • 如:Lua会默认加载math = require(“math”)
      1. --在一个没有使用require的程序中输出package.loadedtable
      2. for k, v in pairs(package.loaded) do
      3. print(k, v)
      4. end
      5. --[[
      6. 输出:
      7. io table: 0000000000e68df0
      8. debug table: 0000000000e69d70
      9. package table: 0000000000e679e0
      10. utf8 table: 0000000000e699f0
      11. _G table: 0000000000e618f0
      12. coroutine table: 0000000000e68ba0
      13. string table: 0000000000e69c30
      14. table table: 0000000000e68b60
      15. math table: 0000000000e69c70
      16. os table: 0000000000e69bf0
      17. 这些表在Lua中的模块都属于标准库
      18. --]]
  • Lua中模块是第一类值,可以像操作普通表一样操作模块

    • 注:前提是模块必须有返回值,否则调用模块返回的是true
  • 可以是定义了一些变量的代码

    • 通常,这些代码返回一个由模块中函数组成的表
      --如math库函数中的部分代码如下
      math = {}        --创建一个表,用于储存模块中的函数
      function math.acos(x) end
      function math.asin(x) end
      ---。。。。
      return math    --返回储存模块中函数的表
      

      require():载入模块

  • require(modename):载入名为modname的模块

    • modename:要载入的模块名
    • 返回值:取决于加载函数
      • 加载函数有返回值:require函数返回该值,并保存package.loaded
      • 加载函数无返回值:require函数返回true,并保存package.loaded
        • 返回true是为了防止模块重复加载
  • require执行步骤
    1. package.loaded检查模块是否已被加载
      1. 已经被加载返回package.loaded中相应的模块被加载后,同意模块调用返回同一值
    2. 模块未加载搜索指定模块的Lua文件(搜索路径package.path指定)
      1. 找到文件:使用loadfile加载Lua文件,并添加package.loaded
    3. Lua文件未找到搜索C标准库(搜索路径package.cpath指定)
      1. 找到C标准库:使用package.loadlib进行加载
  • 注:模块在只能加载一次

    --[[
    强制加载同一模块多次
    --]]
    package.loaded[.modname] = nil    --先将原本的删除
    

    模块的重命名

  • 如果模块名中含有连字符,会被视为使用连字符之前内容来创建luaopen_*函数

    • 如:mod-v3.4模块会被认为luaopen_mod不是luaopen_mod-3.4(更多用于连接C语言库)

      require的搜索路径

  • require使用的路径一组模板

    • 模板:每一个模板都是一个包含可选问号的路径名,会使用模块名替换问好
      • 替换后检查文件是否存在
      • 不存在,尝试下一个模板
    • 如:路径为:?;?.lua;c:\windows\?.lua
      • 使用该路径调用sql模块时,将会打开如下Lua文件
        • sql
        • sql.lua
        • c:\windows\sql.lua
  • require用于搜索Lua文件的路径package.path当前值C标准库的搜索路径为package.cpath

    • package.path初始值来自Lua环境变量

      package.searchers():检查规则

  • package.searchers(modname,path)package.searchers函数实现了搜索库中的所有规则,该函数使用规则,将modname带入path中进行检查

    • modname:模块名
    • path:要检查的搜索库规则
    • 返回值
      • 存在文件,则返回文件名
      • 不存在文件,返回nil及文件的错误信息 ```lua local path1 = “D:\lua-5.4.3-setup\lua\bin\lua\?.lua;.\?\init.lua” local path2 = “D:\lua-5.4.3-setup\lua\bin\lua\?.lua;.\?\init.lua;?.lua”

print(“==cheack path1==”) print(package.searchpath(“test2”, path1)) print(“==check path2==”) print(package.searchpath(“test2”, path2))

—[[ 输出: ==cheack path1== nil no file ‘D:\lua-5.4.3-setup\luin\lua\test2.lua’ no file ‘.\test2\init.lua’ ==check path2== test2.lua —]]

<a name="B0Fml"></a>
#### 搜索器

- **table package.searchers**中**包含**了**require**所使用的**4个搜索器函数**。
   - 在**require**传入**模块名**时,**依次调用搜索器**,**直到找到**指定模块(找不到则抛出异常)
   - **package.searchers中**的函数:
      1. 第**一**个查找器:在**package.preload** table**中查找**
      1. 第**二**个查找器:**查找Lua库的加载库**,在**package.path中查找**。过程与package.searchpath函数一致
      1. 第**三**个查找器:**查找C库的加载库**,在**package.cpath中查找**。过程与package.searchpath函数一致
      1. 第**四**个查找器:**一体化加载器**,从C路径中查找指定模块的根名字
      1. 自己添加的搜索器函数
```lua
package.searchers[5] = function()        --在搜索器table package.searchers中添加第五个搜索器
    print("this is searchers 5")
end

mod = require("null")    --require调用null模块时,会依次使用package.searchers中的搜索器
mod.show()
--[[
输出:(该情况是null模块不存在的情况下)
this is searchers 5
报错信息
..
--]]

编写模块的基本方法

  • 最简单的方法,创建一个表并将所有需要导出的函数放入其中最后返回这个表。 ```lua local M = {} —模块

local function new(r, i) —使用local设定为私有函数 return {r = r, i = i} end

local function inv(c) —使用local设定为私有函数 local n = c.r ^ 2 + c.i ^ 2 return new(c.r / n, -c.i / n) end

M.new = new —将new加入到模块中

M.i = new(0, 1)

function M.add(c1, c2) return new(c1.r + c2.r, c1.i + c2.i) end

function M.sub(c1, c2) return new(c1.r - c2.r, c1.i - c2.i) end

function M.mul(c1, c2) return new(c1.r c2.r, c1.i c2.i) end

function M.div(c1, c2) return M.mul(c1, inv(c2)) end

function M.tostring(c) return string.format(“%g,%g”, c.r, c.i) end

return M
—[[ return的替换方式1: package.loaded[…] = M

return的替换方式2: return{ new =new i=i add=add … } —其中函数及变量全部为模块中的全局变量 —]] ```

  • 注意:如何将new和inv声明为局部变量而使其成为私有函数
  • 其中return M代码段的替换
    1. 可以使用package.loaded[…] = M代替(但是使用return更加清晰)
      • 因为require会将其模块名称作为参数传给加载函数,就是模块名
    2. 还可以将所有函数定义为局部变量,最后返回一个构造的表
      • 优点:无需增加前缀M.。
      • 缺点:导出的表在模块最后,比较冗余

子模块和包

子模块

  • 子模块:Lua利用层次结构的模块名,使用点进行分割

    • 层次结构即可以将模块放在一个文件夹下整个文件夹作为其中一个模块就是包的一个子模块
    • mod.sub模块时mod模块的一个子模块
      • 该组成就是mod文件夹下,有一个名为sub的模块(Lua文件)
    • 加载mod.sub模块时require使用模块名mod.sub作为键查询table package.loadedpackage.preload
      • :在搜索子模块时,Lua会将require中的点转换为其他字符,通常是目录分割符号(如果是c程序则是_,该符号是可配置的)
        • mod.sub会被转换为mod\sub

  • 包:由模块组成的完整的树,发行程序的单位

    • 即将所有模块放在一个文件夹下