Lua中的元表和元方法|果冻想
本文是对于“果冻想”的《Lua入门系列》所作笔记,原文来自上面链接。
前言
比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在lua中是按照以下步骤进行的:
1.先判读a和b两者之一是否有元表;
2.检查该元表中是否有一个叫__add的字段;
3.如果找到了该字段,就调用该字段对应的值,这个值对应的是一个metamethod;
4.调用__add对应的metamethod计算a和b的值
在lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。
lua在创建新的table时不会创建元表,比如以下代码就可以演示:
a = {}
print(getmetatable(a)) -- nil
任何table都可以作为任何值的元表
在lua代码中,只能设置table的元表。若要设置其他类型的值的元表,则必须通过c代码来完成。还存在一个特列,对于字符串,标准的字符串程序库为所有的字符串都设置了一个元表,而其他类型在默认情况下都没有元表。
print(getmetatable("Hello")) -- table: 0072B0C0
print(getmetatable(10)) -- nil
在table中,我们可以重新定义的元方法有以下几个:
__add(a, b) --加法
__sub(a, b) --减法
__mul(a, b) --乘法
__div(a, b) --除法
__mod(a, b) --取模
__pow(a, b) --乘幂
__unm(a) --相反数
__concat(a, b) --连接
__len(a) --长度
__eq(a, b) --相等
__lt(a, b) --小于
__le(a, b) --小于等于
__index(a, b) --索引查询
__newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)
__call(a, ...) --执行方法调用
__tostring(a) --字符串输出
__metatable --保护元表
算术类的元方法
Set = {}
local mt = {}
function Set.new( l )
local set = {}
setmetatable(set, mt)
mt.__add = Set.union
for _, v in pairs(l) do set[v] = true end
return set
end
function Set.union( a, b )
local reSet = Set.new{}
for v in pairs(a) do
reSet[v] = true
end
for v in pairs(b) do
reSet[v] = true
end
return reSet
end
function Set.intersection( a, b )
local retSet = Set.new{}
for v in pairs(a) do retSet[v] = b[v] end
return retSet
end
function Set.toString( set )
local tb = {}
for e in pairs(set) do
tb[#tb + 1] = e
end
return "{" .. table.concat(tb, ", ") .. "}"
end
function Set.print( s )
print(Set.toString(s))
end
require("Set")
local set1 = Set.new({10, 20, 30})
local set2 = Set.new({1, 2})
print(getmetatable(set1))
print(getmetatable(set2))
local set3 = set1 + set2
Set.print(set3)
1.对于二院操作符,如果第一个操作数有元表,并且元表中有所需要的字段定义,比如我们这里的__add元方法定义,那么lua就以这个字段为元方法,而与第二个值无关;
2.对于二元操作符,如果第一个操作数有元素,但是元表中没有所需要的字段定义,比如我们这里的__add元方法定义,那么lua就去查找第二个操作数的元表;
3.如果两个操作数都没有元表,或者都没有对应的元方法定义,lua就引发一个错误。
__tostring元方法
函数print总是调用tostring来进行格式化输出,当格式化任意值时,tostring会检查该值是否有一个__tostring的元方法,如果有这个元方法,tostring就用该值作为参数来调用这个元方法,剩下实际的格式化操作就有__tostring元方法引用的函数去完成,改函数最终返回一个格式化完成的字符串。例如以下代码:
mt.__tostring = Set.toStirng
如何保护我们的“奶酪”——元表
在lua中,函数setmetatable和getmetatable函数会用到元表中的一个字段,用于保护元表,该字段是__metatable。当我们想要保护集合的元表,使用户既不能看也不能修改集合中的元表,那么就需要使用__metatable字段了;当设置了该字段时,getmetatable就会返回改字段的值,而setmetatable则会引发一个错误;如以下演示代码:
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in pairs(l) do set[v] = true end
mt.__metatable = "You cannot get the metatable" -- 设置完我的元表以后,不让其他人再设置
return set
end
local tb = Set.new({1, 2})
print(tb)
print(getmetatable(tb))
setmetatable(tb, {})
__index元方法
当我们访问一个table中不存在的字段是,得到的结果是nil还是是其他值: