前言
在Lua中我们无法直接对两个table进行相加,无法对函数进行比较,也无法调用一个函数。
于是Lua可以通过修改一个值的行为,使其在面对一个非预定义的操作时执行一个自己实现的操作。比如两个table相加,a+b。设a、b均为table,当Lua试图计算a+b时,它会检查两者之一是否有元表,然后检查元表中是否有__add的函数字段,找到了,则调用这个字段对应的值,这个值就是“元方法”,也就是函数。
Lua的每个值都有一个元表。table和userdata可以有各自独立的元表,而其他类型则共享其类型所属的单一元表。但是Lua只支持修改table的元表,其他的元表需要去底层的C代码完成
获取table的元表:
t = {} print(getmetatable(t)) --设置b为a的元表 setmerarable(a,b)
13.1 算术类的元方法
用元方法实现集合的并集和交集:
Set = {} local mt = {} function Set.new(l) local set = {} setmetatable(set, mt) for _,v in pairs(l) do set[v] = true end return set end function Set.union(a, b) local res = Set.new({}) for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection(a, b) local res = Set.new({}) for k in pairs(a) do res[k] = b[k] end return res end function Set.tostring(set) local l = {} for e in pairs(set) do l[#l+1] = e end return "{"..table.concat(l,", ").."}" end function Set.print(s) print(Set.tostring(s)) end mt.__add=Set.union s1=Set.new({10,20,30,50}) s2=Set.new({1,30}) s3 = s1 + s2 mt.__mul=Set.intersection Set.print(s3) Set.print(s1*s2)
除了代码中的__add、__mul还有__sub(减法)__div(除法),__unm(相反数),__mod(取模),__pow(乘幂),__concat等
Lua查找元表规则:先找第一值是否有元表,并且元表中有__add字段,则把这个为元方法计算而跟第二个无关了。反之,如果第二个有元表且有__add字段,则按此为计算函数。都没有,就会引发报错。
13.2 关系类的元方法
__eq(等于)
__lt(小于)
__le(小于等于)
13.3 库定义的方法
tostring
setmetatable
getmetatable
13.4 table访问的元方法
13.4.1 __index元方法
其实当访问一个table中不存在的字段时,Lua的寻找的路径为 去查找一个叫__index的元方法。如果没有,那么访问的结果为nil。否则就从__index里提供最终的结果。
有点像继承里的概念,如果子类没有的属性,会去父类中去寻找。在Lua中,将__index元方法用于实现继承是很普遍的方法。__index不一定是函数,还可以是一个table。当他是函数时,Lua以table和不存在的key作为参数调用此函数。当他是table时,以相同的方式重新访问这个table。
13.4.2 __newindex元方法
当对一个table内不存在的字段赋值时,解释器会查找__newindex元方法。如果__newindex是一个table,则在这个table中赋值。
但是rawset(table,k,v)可以绕过table的所有元方法强制为table赋值
13.4.3 具有默认值的table
通过__index元表实现默认值代码:
function setDefault(t, d)
local mt = {__index = function() return d end}
setmetatable(t, mt)
end
tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)
setDefault函数为所有需要默认值的table创建了一个新的元表,但是有多个table时这样开销就会太大了。如果可以让多个table用同一个默认的元表就很棒了,其实很简单,将上面代码的mt设为全局变量不就ok了,代码如下
local mt = {__index = function(t) return t.___ end}
function setDefault(t, d)
t.___ = d
setmetatable(t, mt)
end
tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)
13.4.4 跟踪table 的访问
实现一个可以监控table变化的功能,
t = {}
local _t = t
t = {}
local mt = {
__index = function(t, k)
print("access to element"..tostring(k))
return _t[k]
end,
__newindex = function(t, k , v)
print("update of ellement"..tostring(k))
_t[k] = v
end
}
setmetatable(t, mt)
t[2] = "hello"
print(t[2])
缺点:1、无法遍历原来的table,也就是_t。2、多个监控就有多个元表,浪费开销
改善:
local index = {}
t = {}
local mt = {
__index = function(t, k)
print("access to element"..tostring(k))
return t[index][k]
end,
__newindex = function(t, k , v)
print("update of ellement"..tostring(k))
t[index][k] = v
end
}
function track(t)
local answer = {}
answer[index] = t
setmetatable(answer, mt)
return answer
end
tmp = {}
tmp = track(tmp)
tmp[2] = "hello"
print(tmp[2])
运行结果:
13.4.5 只读的table
将更新的操作(__newindex)禁止就可以了
function readonly(t)
local answer = {}
mt = {
__index = t,
__newindex = function(t, k ,v)
error("update error")
end
}
setmetatable(answer, mt)
return answer
end
days = {1,2,3}
days = readonly(days)
print(days[1])
days[1]=2
顺带说一下第十四章的知识点
setfenv(f, table):设置一个函数的环境
(1)当第一个参数为一个函数时,表示设置该函数的环境 (2)当第一个参数为一个数字时,为1代表当前函数,2代表调用自己的函数(即上一层函数),3代表调用自己的函数的函数(上一层的上一层函数),以此类推
*性质:函数的环境,其实一个环境就是一个表,该函数被限定为只能访问该表中的域,或在函数体内自己定义的变量。
setfenv举例
--[[
举例1:setfenv(1, env1)
设置当前函数运行环境
以下例子相当于把test1函数的运行环境设置为env1,而env1是空的,所以 print("test1 func") 会报错
]]local env1 = {
}
print("main func")
local test1 = function()
setfenv(1, env1)
print("test1 func")
end
test1()--[[
正确写法
输出:
main func
test1 func
]]
local env1 = {
print = print
}
print("main func")
local test1 = function()
setfenv(1, env1)
print("test1 func")
end
test1()
--[[
举例2:setfenv(2, env1)
设置setfenv函数上一层的函数环境
以下例子相当于把test1函数上一层的test2函数设置环境env1,所以在运行print("test2 func")的时候会报错
]]
local env1 = {
}
print("main func")
local test1 = function()
setfenv(2, env1)
print("test1 func")
end
local test2 = function()
test1()
print("test2 func")
end
test2()
--[[
test2函数内print和test1互换位置则不报错
]]
local env1 = {
}
print("main func")
local test1 = function()
setfenv(2, env1)
print("test1 func")
end
local test2 = function()
print("test2 func")
test1()
end
test2()
getfenv举例
--[[
举例:getfenv(2)
test1函数内将test2函数的环境env1设置自己的运行环境,所以会报错
但是可以正常打印
main func
test2 func
]]
local env1 = {
}
print("main func")
local test1 = function()
local env2 = getfenv(2)
setfenv(1, env2)
print("test1 func")
end
local test2 = function()
print("test2 func")
setfenv(1, env1)
test1()
end
test2()