本文来自: http://manual.luaer.cn/ ,2.8章.html
Lua 中的每一个值均可以用一个 metatable。 这个 metatable 就是一个原始的 Lua table , 它用来定义原始值在特定操做下的行为。 你能够经过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值 的指定操做之行为。 举例来讲,当一个非数字的值做加法操做的时候, Lua 会检查它的 metatable 中 "__add"
域中的是否有一个函数。 若是有这么一个函数的话,Lua 调用这个函数来执行一次加法。函数
咱们叫 metatable 中的键名为 事件 (event) ,把其中的值叫做 元方法 (metamethod)。 在上个例子中,事件是 "add"
而元方法就是那个执行加法操做的函数。编码
你能够经过 getmetatable
函数来查询到任何一个值的 metatable。lua
你能够经过 setmetatable
函数来替换掉 table 的 metatable 。 你不能从 Lua 中改变其它任何类型的值的 metatable (使用 debug 库例外); 要这样作的话必须使用 C API 。spa
每一个 table 和 userdata 拥有独立的 metatable (固然多个 table 和 userdata 能够共享一个相同的表做它们的 metatable); 其它全部类型的值,每种类型都分别共享惟一的一个 metatable。 所以,全部的数字一块儿只有一个 metatable ,全部的字符串也是,等等。debug
一个 metatable 能够控制一个对象作数学运算操做、比较操做、链接操做、取长度操做、取下标操做时的行为, metatable 中还能够定义一个函数,让 userdata 做垃圾收集时调用它。 对于这些操做,Lua 都将其关联上一个被称做事件的指定健。 当 Lua 须要对一个值发起这些操做中的一个时, 它会去检查值中 metatable 中是否有对应事件。 若是有的话,键名对应的值(元方法)将控制 Lua 怎样作这个操做。code
metatable 能够控制的操做已在下面列出来。 每一个操做都用相应的名字区分。 每一个操做的键名都是用操做名字加上两个下划线 '__
' 前缀的字符串; 举例来讲,"add" 操做的键名就是字符串 "__add"
。 这些操做的语义用一个 Lua 函数来描述解释器如何执行更为恰当。orm
这里展现的用 Lua 写的代码仅做解说用; 实际的行为已经硬编码在解释器中,其执行效率要远高于这些模拟代码。 这些用于描述的的代码中用到的函数 ( rawget
, tonumber
,等等。) 均可以在 §5.1 中找到。 特别注意,咱们使用这样一个表达式来从给定对象中提取元方法htm
metatable(obj)[event]
这个应该被解读做对象
rawget(getmetatable(obj) or {}, event)
这就是说,访问一个元方法再也不会触发任何的元方法, 并且访问一个没有 metatable 的对象也不会失败(而只是简单返回 nil)。
"add": +
操做。
下面这个 getbinhandler
函数定义了 Lua 怎样选择一个处理器来做二元操做。 首先,Lua 尝试第一个操做数。 若是这个东西的类型没有定义这个操做的处理器,而后 Lua 会尝试第二个操做数。
function getbinhandler (op1, op2, event) return metatable(op1)[event] or metatable(op2)[event] end
经过这个函数, op1 + op2
的行为就是
function add_event (op1, op2) local o1, o2 = tonumber(op1), tonumber(op2) if o1 and o2 then -- 两个操做数都是数字? return o1 + o2 -- 这里的 '+' 是原生的 'add' else -- 至少一个操做数不是数字时 local h = getbinhandler(op1, op2, "__add") if h then -- 以两个操做数来调用处理器 return h(op1, op2) else -- 没有处理器:缺省行为 error(···) end end end
"sub": -
操做。 其行为相似于 "add" 操做。
"mul": *
操做。 其行为相似于 "add" 操做。
"div": /
操做。 其行为相似于 "add" 操做。
"mod": %
操做。 其行为相似于 "add" 操做, 它的原生操做是这样的 o1 - floor(o1/o2)*o2
"pow": ^
(幂)操做。 其行为相似于 "add" 操做, 它的原生操做是调用 pow
函数(经过 C math 库)。
"unm": 一元 -
操做。
function unm_event (op) local o = tonumber(op) if o then -- 操做数是数字? return -o -- 这里的 '-' 是一个原生的 'unm' else -- 操做数不是数字。 -- 尝试从操做数中获得处理器 local h = metatable(op).__unm if h then -- 以操做数为参数调用处理器 return h(op) else -- 没有处理器:缺省行为 error(···) end end end
"concat": ..
(链接)操做,
function concat_event (op1, op2) if (type(op1) == "string" or type(op1) == "number") and (type(op2) == "string" or type(op2) == "number") then return op1 .. op2 -- 原生字符串链接 else local h = getbinhandler(op1, op2, "__concat") if h then return h(op1, op2) else error(···) end end end
"len": #
操做。
function len_event (op) if type(op) == "string" then return strlen(op) -- 原生的取字符串长度 elseif type(op) == "table" then return #op -- 原生的取 table 长度 else local h = metatable(op).__len if h then -- 调用操做数的处理器 return h(op) else -- 没有处理器:缺省行为 error(···) end end end
关于 table 的长度参见 §2.5.5 。
"eq": ==
操做。 函数 getcomphandler
定义了 Lua 怎样选择一个处理器来做比较操做。 元方法仅仅在参于比较的两个对象类型相同且有对应操做相同的元方法时才起效。
function getcomphandler (op1, op2, event) if type(op1) ~= type(op2) then return nil end local mm1 = metatable(op1)[event] local mm2 = metatable(op2)[event] if mm1 == mm2 then return mm1 else return nil end end
"eq" 事件按以下方式定义:
function eq_event (op1, op2) if type(op1) ~= type(op2) then -- 不一样的类型? return false -- 不一样的对象 end if op1 == op2 then -- 原生的相等比较结果? return true -- 对象相等 end -- 尝试使用元方法 local h = getcomphandler(op1, op2, "__eq") if h then return h(op1, op2) else return false end end
a ~= b
等价于 not (a == b)
。
"lt": <
操做。
function lt_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 < op2 -- 数字比较 elseif type(op1) == "string" and type(op2) == "string" then return op1 < op2 -- 字符串按逐字符比较 else local h = getcomphandler(op1, op2, "__lt") if h then return h(op1, op2) else error(···); end end end
a > b
等价于 b < a
.
"le": <=
操做。
function le_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 <= op2 -- 数字比较 elseif type(op1) == "string" and type(op2) == "string" then return op1 <= op2 -- 字符串按逐字符比较 else local h = getcomphandler(op1, op2, "__le") if h then return h(op1, op2) else h = getcomphandler(op1, op2, "__lt") if h then return not h(op2, op1) else error(···); end end end end
a >= b
等价于 b <= a
。 注意,若是元方法 "le" 没有提供,Lua 就尝试 "lt" , 它假定 a <= b
等价于 not (b < a)
。
"index": 取下标操做用于访问 table[key]
。
function gettable_event (table, key) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then return v end h = metatable(table).__index if h == nil then return nil end else h = metatable(table).__index if h == nil then error(···); end end if type(h) == "function" then return h(table, key) -- 调用处理器 else return h[key] -- 或是重复上述操做 end end
"newindex": 赋值给指定下标 table[key] = value
。
function settable_event (table, key, value) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then rawset(table, key, value); return end h = metatable(table).__newindex if h == nil then rawset(table, key, value); return end else h = metatable(table).__newindex if h == nil then error(···); end end if type(h) == "function" then return h(table, key,value) -- 调用处理器 else h[key] = value -- 或是重复上述操做 end end
"call": 当 Lua 调用一个值时调用。
function function_event (func, ...) if type(func) == "function" then return func(...) -- 原生的调用 else local h = metatable(func).__call if h then return h(func, ...) else error(···) end end end