Lua语言编程学习之路02----第13章 元表与元方法

前言

    在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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙大学ccsu_deer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值