lua3 Metatable(元表)


本文来自: 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
相关文章
相关标签/搜索