Lua 中 metatable

Lua 中的每个值都可以用一个 metatable。 这个 metatable 就是一个原始的 Lua table , 它用来定义原始值在特定操作下的行为。 你可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值 的指定操作之行为。 举例来说,当一个非数字的值作加法操作的时候, Lua 会检查它的 metatable 中 "__add" 域中的是否有一个函数。 如果有这么一个函数的话,Lua 调用这个函数来执行一次加法。

我们叫 metatable 中的键名为 事件 (event) ,把其中的值叫作 元方法 (metamethod)。 在上个例子中,事件是 "add" 而元方法就是那个执行加法操作的函数。

你可以通过 getmetatable 函数来查询到任何一个值的 metatable。

你可以通过 setmetatable 函数来替换掉 table 的 metatable 。 你不能从 Lua 中改变其它任何类型的值的 metatable (使用 debug 库例外); 要这样做的话必须使用 C API 。

每个 table 和 userdata 拥有独立的 metatable (当然多个 table 和 userdata 可以共享一个相同的表作它们的 metatable); 其它所有类型的值,每种类型都分别共享唯一的一个 metatable。 因此,所有的数字一起只有一个 metatable ,所有的字符串也是,等等。

一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为, metatable 中还可以定义一个函数,让 userdata 作垃圾收集时调用它。 对于这些操作,Lua 都将其关联上一个被称作事件的指定健。 当 Lua 需要对一个值发起这些操作中的一个时, 它会去检查值中 metatable 中是否有对应事件。 如果有的话,键名对应的值(元方法)将控制 Lua 怎样做这个操作。

metatable 可以控制的操作已在下面列出来。 每个操作都用相应的名字区分。 每个操作的键名都是用操作名字加上两个下划线 '__' 前缀的字符串; 举例来说,"add" 操作的键名就是字符串 "__add"。 这些操作的语义用一个 Lua 函数来描述解释器如何执行更为恰当。

这里展示的用 Lua 写的代码仅作解说用; 实际的行为已经硬编码在解释器中,其执行效率要远高于这些模拟代码。 这些用于描述的的代码中用到的函数 ( rawget , tonumber ,等等。) 都可以在 §5.1 中找到。 特别注意,我们使用这样一个表达式来从给定对象中提取元方法

     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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值