元方法教程

lua-users home
wiki

本教程简要介绍了 Lua 元方法的概念。

元方法

Lua 拥有强大的扩展机制,允许你重载 Lua 对象上的某些操作。每个重载的对象都与一个包含函数元方法元表相关联;这些函数在适当的时候被调用,类似于许多其他语言中的运算符重载概念。

元表是一个普通的 Lua 表,包含一组元方法,这些元方法与 Lua 中的事件相关联。事件发生在 Lua 执行某些操作时,例如加法、字符串连接、比较等。元方法是普通的 Lua 函数,在特定事件发生时被调用。事件具有诸如“add”和“concat”之类的名称(参见手册第 2.8 节),它们对应于元表中的字符串键,例如“__add”和“__concat”。在这种情况下,要添加(+)或连接(..)两个 Lua 对象。

元表

我们使用函数 setmetatable() 使一个表充当某个对象的元表。

local x = {value = 5} -- creating local table x containing one key,value of value,5

local mt = {
  __add = function (lhs, rhs) -- "add" event handler
    return { value = lhs.value + rhs.value }
  end
}

setmetatable(x, mt) -- use "mt" as the metatable for "x"

local y = x + x

print(y.value) --> 10  -- Note: print(y) will just give us the table code i.e table: <some tablecode>

local z = y + y -- error, y doesn't have our metatable. this can be fixed by setting the metatable of the new object inside the metamethod

当加法运算符发现其操作数不是数字时,它会尝试检查其中一个操作数是否具有一个带有 __add 键的元表。在这种情况下,它确实有,因此它运行存储在元表中该键下的函数,相当于:

local y = (getmetatable(x).__add(x, x)) -- x + x

如果其中一个操作数是数字,元表仍然会使用数学运算符触发。并且左操作数始终是函数的第一个参数,右操作数始终是第二个参数。这意味着具有元方法的表不一定是元方法的第一个参数。

更多事件

以下是 Lua 处理的其他元方法事件的说明。有关元方法事件的完整列表,请参见:元表事件

__index

这是一个非常常用且通用的元方法,它允许你在表中不存在键时运行自定义函数或使用“回退”表。如果使用函数,它的第一个参数将是查找失败的表,第二个参数将是键。如果使用回退表,请记住它可能会在其上触发 __index 元方法(如果它有的话),因此你可以创建长链的回退表。

local func_example = setmetatable({}, {__index = function (t, k)  -- {} an empty table, and after the comma, a custom function failsafe
  return "key doesn't exist"
end})

local fallback_tbl = setmetatable({   -- some keys and values present, together with a fallback failsafe
  foo = "bar",
  [123] = 456,
}, {__index=func_example})

local fallback_example = setmetatable({}, {__index=fallback_tbl})  -- {} again an empty table, but this time with a fallback failsafe

print(func_example[1]) --> key doesn't exist
print(fallback_example.foo) --> bar
print(fallback_example[123]) --> 456
print(fallback_example[456]) --> key doesn't exist

__newindex

当尝试将值赋给表中的键,并且该键不存在(包含 nil)时,会调用此元方法。如果键存在,则不会触发元方法。

local t = {}

local m = setmetatable({}, {__newindex = function (table, key, value)
  t[key] = value
end})

m[123] = 456
print(m[123]) --> nil
print(t[123]) --> 456

比较运算符

__eq 在两个表使用 == 运算符时被调用,如果引用相等性检查失败,并且两个表具有相同的 __eq 元方法 (!)。

__lt 被调用以检查一个对象是否“小于”另一个对象。与 __eq 不同,如果两个对象具有不同的 __lt 元方法,则不会出错,将使用左侧的元方法。

这就是所有比较运算符与您的对象一起工作所需的一切。但是,在某些情况下,__lt__eq 都需要由同一个运算符调用。为了避免这种情况,您可以选择添加 __le(小于或等于)元方法。现在,只有其中一个元方法将在任何比较运算符中被调用。

例如,我们可以像这样改进页面顶部的示例

local mt
mt = {
  __add = function (lhs, rhs)
    return setmetatable({value = lhs.value + rhs.value}, mt)
  end,
  __eq = function (lhs, rhs)
    return lhs.value == rhs.value
  end,
  __lt = function (lhs, rhs)
    return lhs.value < rhs.value
  end,
  __le = function (lhs, rhs) -- not really necessary, just improves "<=" and ">" performance
    return lhs.value <= rhs.value
  end,
}

__metatable

__metatable 用于保护元表。如果您不希望程序更改元表的内容,则设置其 __metatable 字段。这样,程序就无法访问元表(因此也无法更改它)。

元方法手册

请参阅 [1] 以获取所有元方法的列表及其功能说明。

另请参阅


最近更改 · 首选项
编辑 · 历史记录
最后编辑于 2014 年 8 月 16 日上午 7:47 GMT (差异)