【问题标题】:How to create a simple importable class in Lua?如何在 Lua 中创建一个简单的可导入类?
【发布时间】:2017-10-22 12:47:39
【问题描述】:

我想在 Lua 中创建一个单独的文件 myclass.lua 中的 MyClass 类,以后可以导入和使用。 它应该按以下方式工作:

local MyClass = require 'myclass'
tab = {1,2,3}
m = MyClass(tab)

但是,按照 Lua 文档中的代码,我无法使其正常工作,并且出现错误 attempt to call global 'MyClass' (a table value)

到目前为止我为 myclass.lua 编写的代码:

local MyClass = {}
MyClass.__index = MyClass

function MyClass.__init(tab)
    self.tab = tab or {}
    setmetatable({},MyClass)
    return self
end
return MyClass

有很多示例如何在 Lua 中编写类,但我认为我不了解其中的区别,因此迷失在实现细节中。有或多或少的传统方法吗?

【问题讨论】:

    标签: lua lua-table meta-method


    【解决方案1】:

    在 Lua 中,您通常不能像调用函数那样调用表。例如,此代码会产生“尝试调用本地't'(表值)”的错误。

    local t = {}
    t()
    

    但是,有一种方法可以通过使用元表来完成这项工作。

    local hello = {}
    local mt = {} -- The metatable
    mt.__call = function ()
      print("Hello!")
    end
    setmetatable(hello, mt)
    hello() -- prints "Hello!"
    

    当您尝试像调用函数一样调用表时,Lua 首先检查表是否具有元表。如果是这样,那么它会尝试调用该元表的__call 属性中的函数。 __call 函数的第一个参数是表本身,随后的参数是当表作为函数调用时传递的参数。如果表没有元表,或者元表没有__call 函数,则会引发“尝试调用本地't'”错误。

    您的示例代码存在三个问题:

    1. 您正在尝试使用__init 而不是__call。 Lua 没有 __init 元方法。
    2. __call 采用与您正在使用的参数不同的参数。 __call 函数的第一个参数是表本身。您可以使用function MyClass.__call(self, tab),也可以使用冒号语法function MyClass:__call(tab),它会为您隐式添加self 参数。这两种语法在功能上是相同的。
    3. 您尚未为MyClass 表设置元表。当您为 MyClass 的对象设置元表时,这并不意味着会自动为 MyClass 本身设置元表。

    要解决此问题,您可以执行以下操作:

    local MyClass = {}
    setmetatable(MyClass, MyClass)
    MyClass.__index = MyClass
    
    function MyClass:__call(tab)
        local obj = {}
        obj.tab = tab or {}
        setmetatable(obj, MyClass)
        return obj
    end
    
    return MyClass
    

    这将 MyClass 设置为将自己用作元表,这是完全有效的 Lua。

    元表系统非常灵活,允许您拥有几乎任何您想要的类/对象方案。例如,如果您愿意,您可以内联执行所有操作。

    local MyClass = {}
    
    setmetatable(MyClass, {
        __call = function (class, tab)
            local obj = {}
            obj.tab = tab or {}
            setmetatable(obj, {
                __index = MyClass
            })
            return obj
        end
    })
    
    return MyClass
    

    除了简洁之外,这还有一个优点,即如果人们可以访问类表,他们就无法更改类的元方法。

    【讨论】:

    • 将表的元表设置为自身,并将其 __index 元方法设置为自身,这是一个轻微的缺点(好的,一个主要缺点)。 local obj = MyClass{1, 2, 3} 实际上可以很好地使用此代码,但是如果您尝试索引 obj.foo,那么它会看到 fooobj 中不存在,然后检查 MyClass 表中的 foo 为这就是您将 obj 的 __index 设置为的内容。它也没有在MyClass 中找到foo,所以它查看MyClass 的__index,即...MyClass。 Lua 足够聪明,可以检测到这是一个无限循环,因此它会为您报错。
    【解决方案2】:

    没有可用于表的__init 元方法。当您执行以下操作时:

    m = MyClass(tab)
    

    它查找MyClass.__call 方法定义。只需将您的myclass.lua 更新为:

    local MyClass = {}
    MyClass.__index = MyClass
    
    function MyClass:__call(tab)
        self.tab = tab or {}
        setmetatable({},MyClass)
        return self
    end
    
    return MyClass
    

    【讨论】:

    • 是的,但它并没有解决错误的问题。尝试导入这个并调用local m = MyClass({1,2,3})
    • @minerals __call 的第一个参数是被调用的“函数”——在本例中是类。所以在这里把它改成function MyClass.__call( _, tab )。 (如果你以后碰巧有“通用”构造函数,你可以给 '_' 一个不同的名字并用它来找出哪个类 __call 应该构造一个实例。)
    • 它不起作用。我收到attempt to call global MyClass 错误。
    • @nobody 啊,完全忘记了自我参考:/现在编辑
    猜你喜欢
    • 2013-03-28
    • 2021-04-09
    • 2019-06-22
    • 2012-03-04
    • 1970-01-01
    • 2013-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多