元表允许当遇到未知操作时,改变值的行为。例如,使用元表,可以定义表a与表b的关系运算a+b。当lua尝试两个表相加时,会检查是否其中一个有元表并且元表是否有__add字段。
元表在面向对象的术语中是一种受限的类。正如类一样,元表定义实例行为。但是元表有比类更多的限制。仅仅给预定义的操作给定行为,并且元表没有继承。
lua中的每个值可以有元表。表和用户数据有个人的元表。其它类型的值共享单个元表。lua总是创建没有元表的新表。
获取表的元表使用getmetatable,设置使用setmetatable。lua中仅仅可以设置表的元表。要控制其它类型值的元表,必须使用c代码或者debub库。字符串库为字符串设置了元表。其他类型默认是没有元表。
print(getmetatable("hi"))
print(getmetatable("xuxu"))
print(getmetatable(10))
print(getmetatable(print))
输出为:
table: 0000000000dfa170
table: 0000000000dfa170
nil
nil
任何表都可以是任何值的元表。一组相关的表可以共享一个共同的元表,此元素描述了它们的公共行为。表可以是它自己的元表,描述它自己的个人行为。
1、算术元方法
Set模块定义如下
local Set = {}
function Set.new(l)
local set = {}
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
return Set
希望使用算术加法来计算两个集合的交集,此时使用元表来实现。 在模块中定义元表,在创建时设置元表数据
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
return Set
创建的集合会共用同一个元表。
local Set = require('set')
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1))
print(getmetatable(s2))
输出为:
table: 0000000000c89f30
table: 0000000000c89f30
使用+来表示两个集合的并集,*来计算两个集合的交集。即使用元表的__add和__mul
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
mt.__add = Set.union
mt.__mul = Set.intersection
return Set
样例代码为
local Set = require('set')
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
s3 = s1 + s2
print(Set.tostring(s3))
print(Set.tostring(s3 * s1))
输出为:
{1, 20, 10, 30, 50}
{20, 50, 10, 30}
支持的算术操作有
加法 | __add |
乘法 | __mul |
减法 | __sub |
浮点数除法 | __div |
整数除法 | __idiv |
取反操作 | __unm |
求模 | __mod |
求幂 | __pow |
支持的位操作有
位与 | __band |
位或 | __bor |
异或 | __bxor |
位取反 | __bnot |
左移 | __shl |
右移 | __shr |
2、关系运算元方法
支持的操作有
等于 | __eq |
小于 | __lt |
小于等于 | __le |
lua会将a~=b翻译成 not (a == b),a > b翻译成b < a, a >=b 翻译成 b <= a
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
mt.__add = Set.union
mt.__mul = Set.intersection
mt.__le = function (a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
mt.__lt = function(a, b)
return a <= b and not (b <= a)
end
mt.__eq = function(a, b)
return a <= b and b <= a
end
return Set
样例代码如下:
local Set = require('set')
s1 = Set.new{2, 4}
s2 = Set.new{4, 10, 2}
print(s1 <= s2)
print(s1 < s2)
print(s1 >= s1)
print(s1 > s1)
print(s1 == s2 * s1)
输出为:
true
true
true
false
true
3、库定义的元方法
由库来定义元表中的字段。以集合的tostring为例
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
mt.__add = Set.union
mt.__mul = Set.intersection
mt.__le = function (a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
mt.__lt = function(a, b)
return a <= b and not (b <= a)
end
mt.__eq = function(a, b)
return a <= b and b <= a
end
mt.__tostring = Set.tostring
return Set
样例代码如下
local Set = require('set')
s1 = Set.new{10, 4, 5}
print(s1)
输出为:
{4, 5, 10}
如果希望保护元表,用户既不能看到也不能修改元表,可以通过设置元表的__metatable,getmetatable会返回该字段值,但是setmetatable会抛出异常。
local Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in ipairs(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] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
mt.__add = Set.union
mt.__mul = Set.intersection
mt.__le = function (a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
mt.__lt = function(a, b)
return a <= b and not (b <= a)
end
mt.__eq = function(a, b)
return a <= b and b <= a
end
mt.__tostring = Set.tostring
mt.__metatable = "not your business"
return Set
样例代码如下:
local Set = require('set')
s1 = Set.new{10, 4, 5}
print(getmetatable(s1))
setmetatable(s1, {})
输出为:
not your business
E:\lua-5.4.2_Win64_bin\lua54.exe: test.lua:9: cannot change a protected metatable
stack traceback:
[C]: in function 'setmetatable'
test.lua:9: in main chunk
[C]: in ?
4、表访问元方法
访问和修改表中缺省字段
4.1 __index元方法
当访问表中缺省字段时,如果没有提供__index元方法,直接返回 nil,否则元方法提供结果。
__index元方法不一定是函数,也可以是表。
4.2 __newindex元方法
当对一个缺省的字段赋值时,会查找__newindex元方法,解释器会调用它而不是赋值。与__index元方法类似,如果元方法是表,解释器会在表中赋值。