熟练使用Lua(二)语言核心:table及Metatable、MetaMethod操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_24459491/article/details/82988397

table

table 类型实现了一个关联数组。也就是说,数组可以用任何东西(除了nil)做索引,而不限于数字。 table 可以以不同类型的值构成;它可以包含所有的类型的值(除 nil 外)。 table 是 lua 中唯一的一种数据结构;它可以用来描述原始的数组、符号表、集合、记录、图、树、等等。用于表述记录时,lua 使用域名作为索引。语言本身采用一种语法糖,支持以 a.name 的形式表示 a[“name”]。有很多形式用于在 lua 中创建一个 table。

跟索引一样, table 每个域中的值也可以是任何类型(除 nil外)。特别的,因为函数本身也是值,所以 table 的域中也可以放函数。这样 table 中就可以有一些 methods 了。

**

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

相关链接:
lua中文手册链接速查 - 中国lua开发者 - lua论坛

猜你喜欢

转载自blog.csdn.net/qq_24459491/article/details/82988397
今日推荐