元表使用部分讲解
本文主要用于自己学习内容的总结,有错误欢迎指出,谢谢。
元表的作用
元表的目的是用来定义table 和 userdata 的表 (大概像一个使用说明)
--两种常用的方法
setmetatable(table,setmetatable) --对指定 table 设置元表(metatable),如果元表(metatable)中存在
__metatable 键值,setmetatable 会失败。
getmetatable(table) --返回对象的元表(metatable)。
举个列子:
local t1 ={1}
local t2 ={2}
local t3 = t1 + t2
运行上面的代码就会报错,因为我们执行了两个table 相加的操作,但是Lua并不知道如何将两个table相加。此时我们就需要重新定义table的运算规则,有点像c语言中重载struct的操作符。
算术类的元方法
假设我们使用table作为集合,并且使用一些函数用来计算集合的交集和并集。为了保持命名空间的整齐,将这些函数写入到名为Set的table中。
Set = {}
function Set.new(l) --根据参数列表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] = e
end
return "{"..table.concat(l.",").."}"
end
function Set.print(s)
print(Set.tostring(s))
end
现在我们使用 + 表示计算两个集合的交集,那么此时我就需要让这两个集合共用一个元表,并且要在元表中定义如何执行加法的操作。
local mt = {} --集合的元表
此时我们需要修改Set.new函数。该函数用于创建集合,在新版本中加入一行,将mt设置为当前创建table的元表:
function Set.new(l)
local set = {}
setmetatable(set,mt)
for _,v in ipairs(l) do set[v] = true end
return set
end
--此时我们使用Set.new所创建的集合都共用了一个元表(mt)
s1 = Set.new(10,20,30,50)
s2 = Set.new(40,60)
--此时我们需要把元方法加入到元表中,__add 这个字段描述将加法加入到这个元表中。
--用+表示两个集合的并集
mt.__add = Set.union
s3 = s1 + s2
Set.print(s3) --->(10,20,30,40,50,60)
--类似用*表示两个集合相交,只需把 __mul 加入到元表中
mt.__mul = Set.intersection
s3 = (s1+s2)*s1
Set.print(s3) -->(10,20,30,50)
算术元方法
函数 | 描述 |
---|---|
__add | 运算符 + |
__sub | 运算符 - |
__mul | 运算符 * |
__div | 运算符 / |
__pow | 运算符 ^(幂函数) |
__mod | 运算符 % |
__unm | 运算符 -(取反) |
__concat | 运算符 …(连接) |
关系类的元方法
和算术类基本相同。但是有三种是关系运算符是不存在的(~=,>,>=)。Lua中会把 ~= 转换为 not (a==b),a>b 转化为b<a,将a>=b 转化为了b<=a。以上面集合为例,在集合操作中,通常<=表示集合间的包含关系:a<=b通常意味着a是b的一个子集。
关系元方法
函数 | 描述 |
---|---|
__eq | 运算符 == |
__lt | 运算符 < |
__le | 运算符 <= |
我们来具体实现一下代码:
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 = funtion (a,b)
return a<=b and not (b<=a)
end
mt.__eq = function (a,b)
return a<=b and b<=a
end
s1 = Set.new{2,4}
s2 = Set.new{2,4,10}
print(s1 <= s2) -->true
print(s1 < s2) -->true
print(s1 >= s1) -->true
print(s1 > s2) -->false
print(s1 = s2 * s1) -->true
算术类和关系类不同
关系类的元方法不能缓和类型。如果尝试用一个字符串比较数字类型的话,Lua就会报错。
库定义的元方法
各种程序库在元表中定义他们自己的字段是很普遍的方法。到目前所有的元方法只针对于Lua的核心,也就是一个虚拟机。它会检测一个操作中的值是否有元表,这些元表中是否定义了关于此操作的元方法。从某一方面说,元表也是一种常规的table,任何人,任何函数都可以使用它。
函数tostring就是一个经典案例,tostring可以将各种类型的值表示为简单的文本格式:
print({}) --> table:
函数print总是调用tostring来格式化输出。当格式化任意值时,tostring会检查该值是否有一个__tostring的元方法,如果有这个元方法,tostring就用该值作为参数来调用这个元方法。接下来由这个元方法来完成工作,其返回结果也是tostring的结果。接下来我们来使用上面写到的代码:
mt.__tostring = Set.tostring
s1 = Set.new{10,2,5}
print(s1) --> {2,5,10}
call函数可以让table当作函数来访问:
function Set.call(...)
for _,v in ipairs {...} do
print(v)
end
end
mt.__call = Set.call
s1 = Set.new{}
s1(1,2,3,4) -->1,2,3,4
函数 | 描述 |
---|---|
__tostring | 转化为字符串类型 |
__call | 将table当作函数 |
table 访问的元方法
Lua的表本质其实是个类似HashMap的东西,其元素是很多的Key-Value对。如果尝试访问了一个表中并不存在的元素时,就会返回一个nil值,这样说不全对,实际上当访问table中不存在的字段时,会使解释器去查找一个__index的元方法。如果这个元方法不存在,才会返回这个nil值。而且__index这个元方法和前面不同,__index即可以是个函数,也可以是一个table。
__index作为函数方法实现:
function Set.index(t,key) --t表示自己,key表索引
return "not_find_"..key
end
mt.__index = Set.index
s1 = {1,2,3}
print(s1.key) -->nil
s2 = Set.new{1,2,3}
print(s2.key) --> not_find_key
__index作为table实现:
mt.__index = {key = "not_find_key"}
s1 = Set.new{1,2,3}
print(s1.key) --> not_find_key
print(s1.key2) --> nil
__index作为table 和函数时效果相似,但也不一样,如上面的例子。因为我们的key2 没有定义,所以Lua再查找时,就返回了nil。
除了__index的元方法,table中还存在一种__newindex的元方法。两者相似但又不同,前者用于查询,而后者则用于table的更新。如果__newindex元方法是一个table,解释器就会在这个table中赋值,而不是原来的table。
作为函数时:
mt.__newindex = function(t,index,value)
print("index is"..index)
print("value is"..value)
end
s1 = {key = "it is key"}
setmetatable = (s1,mt)
print(s1) --> it is key --表中存在索引key,直接输出
s1.newKey = 10
print(s1.newKey) -->index is newKey --表中并不存在newKey索引,所以调用元方法。
-->value is 10
-->nil
作为table时:
local newTable = {}
mt.__newindex = newTable
t={}
setmetatable(t,mt)
print(t.key,newTable.key) --> nil nil
t.key = "it is key"
print(t.key,newTable.key) --> nil it is key
函数 | 描述 |
---|---|
__index | 访问一个索引 |
__newindex | 给索引赋值 |