metatable是Lua中的重要概念。每一个table都可以加上metatable。meatable可以改变相应的table的行为。让我们看一个例子:
t = {}
mt = {}
setmetatable(t,mt)
getmetatable(t)
使用?getmetatable
?和?setmetatable
?来查看和设定metatable。当然,上面的代码也可以压缩成一行:
t = setmetatable({},{})
这是因为?setmetatable
?会返回它的第一个参数。
metatable可以包括任何东西,metatable特有的键一般以?__
?开头,例如__index
?和?__newindex
?,它们的值一般是函数或其他table。
t = setmetatable({},{
__index = function(t,key)
if key == "foo" then
return 0
else
return table[key]
end
end
})
__index
这是metatable最常用的键了。
当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的?__index
?键。如果?__index
?包含一个表格,Lua会在表格中查找相应的键。
other = { foo = 3 }
t = setmetatable({},{ __index = other })
t.foo
t.bar
如果?__index
?包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__newindex
类似?__index
?,?__newindex
?的值为函数或table,用于按键赋值的情况。
other = {}
t = setmetatable({},{ __newindex = other })
t.foo = 3
other.foo
t.foo
t = setmetatable({},{
__newindex = function(t,key,value)
if type(value) == "number" then
rawset(t,key,value * value) else rawset(t,value) end end }) t.foo = "foo" t.bar = 4 t.la = 10 t.foo -- "foo" t.bar -- 16 t.la -- 100
上面的代码中使用了?rawget
?和?rawset
?以避免死循环。使用这两个函数,可以避免Lua使用?__newindex
?。
运算符
利用metatable可以定义运算符,例如?*
?:
t = setmetatable({ 1,2,153)">3 },{
__mul = function(t,other)
new = {}
for i = do
for _,v in ipairs(t) do table.insert(new,v) end
end
return new
end
})
t = t * 2 -- { 3,153)">3 }
和?__index
?、?__newindex
?不同,?__mul
?的值只能是函数。与?__mul
?类似的键有:
-
__add
?(+)
-
__sub
?(-)
-
__div
?(/)
-
__mod
?(%)
-
__unm
?取负
-
__concat
?(..)
-
__eq
?(==)
-
__lt
?(?<
?)
-
__le
?(?<=
?)
__call
__call
?使得你可以像调用函数一样调用table:
call = function(t,a,b,c,whatever) return (a + b + c) * whatever end }) t(4) -- 24
这是很有用的特性。需要以直接调用table的形式调用table中的某个(默认)函数的时候,使用?__call
?设定很方便。例如,?kikito?的?tween.lua?,就用了这个技巧,这样直接调用?tween
?就可以调用?tween.start
?。再如?MiddleClass?中,类的?new
?方法可以通过直接调用类的方式调用。
__tostring
最后讲下?__tostring
?,它可以定义如何将一个table转换成字符串,经常和print
?配合使用,因为默认情况下,你打印table的时候会显示table: 0x<16进制数字>
?:
t = setmetatable({ 1,2,3 },{
__tostring = function(t)
sum = 0
for _,v in pairs(t) do sum = sum + v end return "Sum: " .. sum end }) print(t) -- prints out "Sum: 6"
例子
综合运用以上知识,我们编写一个2D矢量类:
Vector = {}
Vector.__index = Vector
function Vector.__add(a,b) if type(a) == "number" then return new(b.x + a,b.y + a) elseif type(b) == "new(a.x + b,a.y + b) else new(a.x + b.x,a.y + b.y) end function __sub(a,0); font-weight:bold">new(b.x - a,b.y - a) new(a.x - b,a.y - b) new(a.x - b.x,a.y - b.y) __mul(a,0); font-weight:bold">new(b.x * a,b.y * a) new(a.x * b,a.y * b) new(a.x * b.x,a.y * b.y) __div(a,0); font-weight:bold">new(b.x / a,b.y / a) new(a.x / b,a.y / b) new(a.x / b.x,a.y / b.y) __eq(a,0); font-weight:bold">a.x == b.x and y == y __lt(a,0); font-weight:bold">x < or (a.x == b.x and a.y < b.y) __le(a,0); font-weight:bold">x <= y <= __tostring(a) return "(" .. a.x .. "," .. a.y .. ")" new(x,y) setmetatable({ x = x or 0,y = y or 0 },Vector) distance(a,0); font-weight:bold">return (b - a):len() Vector:clone() new(self.x,self.y) unpack() self.x,math.sqrt(self.x * self.x + self.y * self.y) lenSq() x * x + y * normalize() local len = self:x = x / len y = y / self normalized() self / rotate(phi) c = cos(phi) s = sin(phi) c * x - s * rotated(phi) clone():perpendicular() new(-self.y,self.x) projectOn(other) return (self * other) * other / other:cross(other) other.y - setmetatable(Vector,{ __call = function(_,...) return Vector.new(...) end })