Lua 基础之模块

这篇博客介绍了Lua中的模块系统,包括如何使用require函数加载模块,模块的编写方法,如返回table,模块定义与使用分离,以及自动模块名和隐藏返回值的概念。还探讨了使用环境来避免全局污染,并提到了lua5.1之前的module函数用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模块

模块系统的设计目标是可以使用不同的方式来共享代码,一个模块就是一个代码库,其它模块 可以使用 require 函数来加载,加载后得到模块导出的所有东西,如函数、常量、全局变量等。一个比较好的方式是让模块返回一个 table 并保存在一个全局变量中,然后外部模块直接使用这个全局变量来操作 table

模块定义–mod.lua

demo = {}

demo.var = 100

demo.foo = function()
    print("hello lua")
end

return demo

使用模块–main.lua

local m = require("mod")

m.foo()
demo.foo()

print(m.var, demo.var)

require 函数

require 函数用于加载一个模块,其实现细节如下

function require(name)
    if not package.loaded[name] then
        local loader = findloader(name)
        if loader == nil then
            error("unable load module")
        end
        package.loaded[name] = true
        local res = loader(name)
        if res ~= nil then
            package.loader[name] = res
        end
    end
    return package.loaded[name]
end
  • 当加载一个模块时,会先在表 package.loaded 中查找该模块是否已经加载,如果已经加载了直接返回上次加载后的结果,因此重复调用 require 来加载同一个模块是不会出问题的
  • 如果模块未加载,则试着为模块查找一个加载器,这个过程是在表 package.preload 中查询模块名,如果在其中找到一个函数则把该函数作为模块的加载器,上面代码使用 findloader 来抽象这个过程
  • 接下来就是使用加载器来加载模块了,加载之前先把 package.loaded[name] 的值设为 true 这样可以避免两个模块交叉引用时陷入死循环;加载完成后把模块返回的值保存下来,如果模块没有显式地返回某个值,则返回的结果为 true
  • 最后返回 package.laoder[name] 的值

编写模块的方法

返回一个 table

complex = {}

complex.__index = complex

complex.new = function(self, r, i)
    local o = {}
    setmetatable(o, self)
    o._r = r or 0
    o._i = i or 1
    return o
end

complex.add = function(self, c1, c2)
    return complex:new(c1._r + c2._r, c1._i + c2._i)
end

complex.get_r = function(self)
    return self._r
end

complex.get_i = function(self)
    return self._i
end

complex.set_r = function(self, v)
    self._r = v
end

complex.set_i = function(self, v)
    self._i = v
end

complex.i = complex:new(0, 1)

return complex

使用 require 函数加载模块后会得到一个全局变量 complex,它的值就是模块定义的 table,通过 complex 变量就能访问和操作整个模块,这是模块最基本的写法

模块定义与使用分离

上面的例子中模块定义直接使用导出的模块名,这种方式看起来不是很规范,一种改进方法是将模块定义与导出的模块名分离,可以在模块内使用局部来定义 table,再将局部变量的值赋给导出变量

local M = {}

M.__index = M

function M:new(r, i)
    local ins = {}
    setmetatable(ins, self)
    ins._r = r or 0
    ins._i = i or 1
    return ins
end

function M:add(other)
    return M:new(self._r + other:get_r(), self._i + other:get_i())
end

function M:get_i()
    return self._i
end

function M:get_r()
    return self._r
end

function M:set_i(v)
    self._i = v
end

function M:set_r(v)
    self._v = v
end

M.i = M:new(0, 1)

complex = M

return complex

自动模块名 & 隐藏返回值

local M = {}
local mod_name = ...

_G[mod_name] = M
package.loaded[mod_name] = M

M.__index = M

function M:new(r, i)
    local ins = {}
    setmetatable(ins, self)
    ins._r = r or 0
    ins._i = i or 1
    return ins
end

function M:add(other)
    return M:new(self._r + other:get_r(), self._i + other:get_i())
end

function M:get_i()
    return self._i
end

function M:get_r()
    return self._r
end

function M:set_i(v)
    self._i = v
end

function M:set_r(v)
    self._v = v
end

M.i = M:new(0, 1)

_G[mod_name] = M 创建了一个全局变量 mod_name = M
模块没显式写返回值,默认会返回 package.loaded[mod_name]

使用环境

现在定义模块都是在全局环境中完成时,这时不管数据的定义还是使用都得加上对应的前缀,而且如果定义私有变量时忘记加上 local 就会污染到全局环境。函数环境 让模块独占一个环境,这个环境与全局环境完全隔离,此时对模块而言,全局环境就是它自己的环境,因此它不能访问到真正的全局环境,也就是说不能访问其它基本模块,如果要使用全局环境中的模块,可以在使用 函数环境 前把这些模块保存到局部变量
使用 函数环境 后函数和变量的定义和使用都不需要加上模块前缀了
lua5.1 之前使用 函数环境 的方式是 setfenv(1,mod),5.3 的方式是 ENV = mod

local M = {}

-- 模块名
local mod_name = ...

-- 保存到全局变量
_G[mod_name] = M

-- 保存到已加载模块
package.loaded[mod_name] = M

local setmetatable = setmetatable

-- 使用“函数环境”
-- setfenv(1, M)    -- 5.1
local _ENV = M      -- 5.3

__index = M

new = function(self, r, i)
    local o = {}
    setmetatable(o, self)
    o._r = r or 0
    o._i = i or 1
    return o
end

add = function(self, other)
    return new(M, self._r + other._r, self._i + other._i)
end

get_i = function(self)
    return self._i
end

get_r = function(self)
    return self._r
end

set_i = function(self, v)
    self._i = v
end

set_r = function(self, v)
    self._v = v
end

module 函数

使用 module 函数可以简化上面的模块定义

module(...)

等价于

local mod_name = ...
_G[mod_name] = M
package.loaded[mod_name] = M
setfenv(1, M) 

如果想访问原来的全局环境,则使用
module(... , package.seeall)
注意: 这是 lua5.1 之前定义模块的方法,5.3 并不建议使用 函数环境,这种方式会定义一个该模块的 table,然后注入到全局环境,这样虽然模块内的东西不会污染到全局环境,但整个模块会污染到全局环境,因为没有引用该模块的文件也能访问该模块内的函数或变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值