Metatable和表
在使用lua
的时候,lua
的表是最经常用到的,虽然表已经提供给我们非常多的操作,包括变量,函数等,Metatable
的引入可以改变相应的表行为,使得表更加的灵活。
有时我们需要对表中新创建的元素进行跟踪,使用Metatable可方便的做到这一点。带着这个问题,看看是如何使用Metatable 做到这一点的。
__index和**__newindex**
如下先创建一个带Metatable
-- 声明一个正常的关系变量
lo_table = {}
-- 声明空元表变量
lo_meta_table = {}
-- 为关系变量t设置元表变量
setmetatable(lo_table, lo_meta_table)
-- 获取一个关系变量的元表变量
getmetatable(lo_table)
setmetatable接收了两个参数,这两个参数都是表,lo_meta_table
变成了lo_table
的 元表变量。后面对lo_meta_table
的操作,会影响到lo_table
,元表lo_meta_table
可以有哪些操作呢?
最常见的是 __index和**__newindex**。
__index和**__newindex** 在表元素被访问和创建新元素的时候会被调用:
lo_table = setmetatable({}, {
__index = function(lo_table, key)
if key == "foo" then
return 0
else
return table[key]
end
end
,
__newindex = function(t, key, value)
rawset(t, key, value)
end
})
lo_table.x = 2
print(lo_table.x)
print(lo_table.foo)
这也就回答了上面的问题,表中的元素在被访问和创建的时候会调用元表中的**__index和__newindex**。通过控制这两个函数,就能跟踪这个过程。
同样的在**__index**中也可以设置函数方法。
-- 创建元表变量
lo_meta_table = {
action = function ( param )
-- body
if(param == "学习") then
print("阿猫学编程")
else
print("python")
end
end
}
-- 设置该元表变量作为关系变量的
lo_table = setmetatable({}, { __index = lo_meta_table })
-- 打印lo_table变量的动作 阿猫学编程
print(lo_table.action("学习"))
-- 打印lo_table变量的动作 python
print(lo_table.action("编程"))
如上创建了一个方法action
,在调用lo_table
时,相当于拥有了一个action
的方法。
__call
__index和**__newindex相类似的方法还有__call**。__call使得你可以像调用函数一样调用table。
t = setmetatable({}, {
__call = function(t, a, b, c, whatever)
return (a + b + c) * whatever
end
})
local result = t(1, 2, 3, 4)
print(result)
实现了**__call函数以后,在进行构造result
的时会调用__call**方法。这时返回的不是一个表而是一个数值。
__tostring
__tostring,它可以定义如何将一个table转换成字符串,经常和 print 配合使用,因为默认情况下,你打印table的时候会显示 table: 0x7f86f3d04d80 这样的代码。
lo_table = setmetatable({ 1, 2, 3 }, {
__tostring = function(lo_table)
sum = 0
for _, v in pairs(lo_table)
do
sum = sum + v
end
return "计算的结果是: " .. sum
end
})
-- prints out "计算的结果是: 6"
print(lo_table)
运算符
利用metatable可以定义运算符,例如+:
-- 创建重载+号行为的表变量
lo_table = setmetatable({ 1, 2, 3 }, {
__add = function(lo_table, other)
new = {}
-- 遍历元素加other
for _, v in ipairs(lo_table)
do table.insert(new, v + other)
end
return new
end
})
-- 进行计算+
lo_table = lo_table + 2
-- 打印得到的结果
print(lo_table[1])
print(lo_table[2])
print(lo_table[3])
__add 函数遍历了所有的数组,并把每个数组的元素加上2。当然这个函数可以定义为所需要的功能。
和__index 、__newindex 不同,__mul 的值只能是函数。与__mul类似的键有:
- __add (+)
- __sub (-)
- __div (/)
- __mod (%)
- __unm 取负
- __concat (…)
- __eq (==)
- __lt (<)
- __le (<=)
创建向量Vector类
通过使用Metatables,使得表变得更加的灵活,他能够做的事情更多了,实现一些复杂点的类也轻松了许多。这里创建一个向量类。
Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({ x = x or 0, y = y or 0 }, Vector)
end
-- __call关键字
setmetatable(Vector, { __call = function(_, ...) return Vector.new(...) end })
这里使用了两种方式实现Vector类的创建,一种是Vector.new(x, y)
,另外一种实现了Vector的__call方法。
在下面中实现向量的加法运算。
-- + 运算符
function Vector:__add(other)
-- ...
local result = Vector(self.x + other.x,self.y + other.y)
return result
end
-- __tostring关键字
function Vector:__tostring()
-- body
return "x: " .. self.x .. " y: " .. self.y
end
创建两个向量a和b,做a和b的和运算,并打印出:
a = Vector.new(12, 10)
b = Vector(20, 11)
c = a + b
print(a)
print(c)