相信你对
lua table已经十分熟悉了,元表是一个普通的table,普通到你可以像操作其他表一样随意的读写表中的数据。然而它又是特殊的,特殊到你可以利用它给表添加算术运算操作,不知道这些骚操作有没有勾起你一丝学习的欲望呢?
元表的价值是如何体现的?
--- 【code】 1-1--- 定义表mt,mt中包含一个test方法和一个name变量local mt = { name = "元表"}function mt:test() print("this is a test function")end--- 定义一个tab,内容为空local tab = {}--- 让mt作为tab的元表setmetatable(tab, mt)--- 设置mt的__index元方法指向mt自己,这一步必不可少,后续会有介绍mt.__index = mt
以上代码中我们将
mt作为
tab的元表,按照OC或java来描述就是:我们将mt设置成了tab的类,tab是类mt的对象,因此tab不仅可以调用
test()方法,也可以访问
name变量。
print(tab.name)tab:test()--- 执行结果momo@MOMOdeMacBook-Pro Desktop $ lua mt.lua元表this is a test functionmomo@MOMOdeMacBook-Pro Desktop $
为了强化类和对象的概念,以后我们称元表为类,以某个表A为元表的表B为A对象(有点绕?)
setmetatable(tab, mt)就是设置tab为类mt的对象
getmetatable(tab)就是获取对象tab的所属类
要清楚的是:这里说的无论对象和类其本质都是一个lua表,就像OC中的对象和类本质都是结构体一样,只是类稍特殊一点而已,要不为啥它有个类对象的绰号呢。
通过这个例子我们可以得出一个结论:元表可以在两个表间实现类和对象的关系
元方法是什么?
比如给string添加加法运算:
--- 【code】 2-1--- 支持两个字符串执行+操作,如 "100" + "1"--- @return numberstring.__add = function(s, num) local a = tonumber(s) or 0 local b = tonumber(num) or 0 return a + bendlocal result = "100" + "200"print(type(result), result) -- > number 300.0
其他还有很多感兴趣的话可以自行搜索一下。
通过这个例子相信你已经对元方的作用有了些自己的体会。
十分重要的元方法`__index` 和 `__newindex`
__index控制着表的读权限,__newindex控制着表的写权限。
默认情况下__index和__newindex都指向nil,如需对表进行访问控制,则需要实现__index和__newindex的指向。
__index是如何影响对象访问类中各个key的:
按照code 1-1的代码来讲tab是怎样访问到mt的name字段的分析:1. tab中不包含name字段2. mt是tab的元表3. mt的__index指向mt自己tab.name的访问顺序:1. 若tab中有name则返回,否则转向mt2. 访问mt的__index,若没有__index指向则返回nil, 若__index指向一个表,则访问该表中的name字段
若对象访问一个自身不存在的key,则访问类(元表)的__index指向,因为一般情况下__index指向类(元表)自己,因此是在类(元表)中尝试访问该key。如果类(元表)中也不存在这个key呢?如果类(元表)也拥有父类(元表),则尝试访问其父类(元表)的__index,以此类推。是不是有点继承的意思呢?
当__index指向一个function时
对象访问一个key的操作实际上有两个参数,一个是对象,另一个是key。因此如果__index指向一个函数,例如:mt = { __index = function(obj, key) -- do something return value end}
__index指向的函数是有两个参数的,第一个是要访问的表,第二个是要访问的key,当__index指向function时将会执行这function,并返回其return的结果。
使用__index指向function可以对类的某些字段进行访问控制
可能存在一种需求:撇开对元表的访问,直接返回该table是否拥有某些值该怎么办?
这个时候我们就需要将__index指向一个function,当然这还不够,lua还提供了一个避免访问元表的访问方式 rawget(tab, key)mt = { __index = function(obj, key) -- 返回obj中key对应的值,若obj中不存在key对应的记录则返回nil,否则返回对应的值 -- 若此处使用return obj[key]则会造成死循环 return rawget(obj, key) end}
__newindex是如何影响对像存值的
对象是个lua表,可以随意在其中保存字段。按照code 1-1的代码来讲是怎样给tab写入name字段的例如:tab.name = "momo"分析:1. tab中不存在name字段2. tab有元表mt3. mt中无__newindex指向代码tab.name = "momo"的访问顺序1. tab中有name字段则直接在tab中添加键值,操作完成,否则步骤22. 访问tab的元表mt,若无则直接在tab中添加键值,操作完成,否则步骤33. 访问tab的__newindex指向,若无直接在tab中添加键值,否则步骤44. 若__newindex指向一个表,则在该表中写入键值;若指向一个function,则执行该function
对标的写操作涉及到三个参数,分别是:被操作表、key、value。
即__newindex对应的函数有三个参数:
mt = { __newindex = function(obj, key, value) -- 同rawset(obj, key),rawset方法将摒弃元表的影响,直接向表obj中写入键值 -- 若此处使用obj[key] = value将造成死循环 rawset(obj, key, value) -- 无需返回值}
__newindex使用较少,一般在有对表写入访问控制的情况下才用
以上 ~
写在最后:其实只要付出了还是会有回报的,它可能会迟到但绝不会缺席。