元方法教程 |
|
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
元方法(如果它有的话),因此你可以创建长链的回退表。
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
当尝试将值赋给表中的键,并且该键不存在(包含 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
字段。这样,程序就无法访问元表(因此也无法更改它)。
请参阅 [1] 以获取所有元方法的列表及其功能说明。