-- 1、检查是否有元表 local t = {1, 2} print(getmetatable(t)) -- nil print("----------------------") -- 2、设置元表 local t = {} print(getmetatable(t)) -->nil
local t1 = {} setmetatable(t, t1) print (getmetatable(t)) assert(getmetatable(t) == t1) print("----------------------") -- 3、常用元方法 --[[ __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 --保护元表 --]]
local tmeta_complext= {} local TComplex={} function TComplex.new(a,b) tComplex = {a,b} setmetatable(tComplex, tmeta_complext) return tComplex end
function TComplex.add(c1,c2) tcomplext = {0,0} setmetatable(tcomplext, tmeta_complext) if c1 and c1[1] and type(c1[1]) == "number" then tcomplext[1] = tcomplext[1] + c1[1] end if c1 and c1[2] and type(c1[2]) == "number" then tcomplext[2] = tcomplext[2] + c1[2] end if c2 and c2[1] and type(c2[1]) == "number" then tcomplext[1] = tcomplext[1] + c2[1] end if c2 and c2[2] and type(c2[2]) == "number" then tcomplext[2] = tcomplext[2] + c2[2] end return tcomplext end
function TComplex.concat(c1,c2) return TComplex.add(c1,c2) end
function TComplex.tostring(c1) return "{" ..c1[1]..","..c1[2].."}" end
function TComplex.print(t) print (TComplex.tostring(t)) end
tmeta_complext.__add = TComplex.add tmeta_complext.__tostring = TComplex.tostring tmeta_complext.__index = function(t, k) return "hei..hei, not accessiable" end tmeta_complext.__newindex = function (t, k, v) print("Attempt to update a read-only field.") end local t3 = TComplex.new(3,1) local t4 = TComplex.new(4,1)
t4 = t3 + t4 TComplex.print(t4) print (t4) print (TComplex.tostring(t3)) print (t3[1],t3[6]) t3[6] = 7 print (t3[1],t3[6])
metatable是Lua中的重要概念。每一个table都可以加上metatable。meatable可以改变相应的table的行为。让我们看一个例子:
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
如果 __index 包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__newindex
类似 __index , __newindex 的值为函数或table,用于按键赋值的情况。
other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3
other.foo
上面的代码中使用了 rawget 和 rawset 以避免死循环。使用这两个函数,可以避免Lua使用 __index 和 __newindex 。
运算符
利用metatable可以定义运算符,例如 * :
t = setmetatable({ 1, 2, 3 }, {
__mul = function(t, other)
new = {}
for i = 1, other do
for _, v in ipairs(t) do table.insert(new, v) end
end
return new
end
})
t = t * 2
和 __index 、 __newindex 不同, __mul 的值只能是函数。与 __mul 类似的键有:
-
__add (+)
-
__sub (-)
-
__div (/)
-
__mod (%)
-
__unm 取负
-
__concat (..)
-
__eq (==)
-
__lt ( < )
-
__le ( <= )
__call
__call 使得你可以像调用函数一样调用table:
t = setmetatable({}, {
__call = function(t, a, b, c, whatever)
return (a + b + c) * whatever
end
})
t(1, 2, 3, 4)
这是很有用的特性。需要以直接调用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)
例子
综合运用以上知识,我们编写一个2D矢量类:
Vector = {}
Vector.__index = Vector
function Vector.__add(a, b)
if type(a) == "number" then
return Vector.new(b.x + a, b.y + a)
elseif type(b) == "number" then
return Vector.new(a.x + b, a.y + b)
else
return Vector.new(a.x + b.x, a.y + b.y)
end
end
function Vector.__sub(a, b)
if type(a) == "number" then
return Vector.new(b.x - a, b.y - a)
elseif type(b) == "number" then
return Vector.new(a.x - b, a.y - b)
else
return Vector.new(a.x - b.x, a.y - b.y)
end
end
function Vector.__mul(a, b)
if type(a) == "number" then
return Vector.new(b.x * a, b.y * a)
elseif type(b) == "number" then
return Vector.new(a.x * b, a.y * b)
else
return Vector.new(a.x * b.x, a.y * b.y)
end
end
function Vector.__div(a, b)
if type(a) == "number" then
return Vector.new(b.x / a, b.y / a)
elseif type(b) == "number" then
return Vector.new(a.x / b, a.y / b)
else
return Vector.new(a.x / b.x, a.y / b.y)
end
end
function Vector.__eq(a, b)
return a.x == b.x and a.y == b.y
end
function Vector.__lt(a, b)
return a.x < b.x or (a.x == b.x and a.y < b.y)
end
function Vector.__le(a, b)
return a.x <= b.x and a.y <= b.y
end
function Vector.__tostring(a)
return "(" .. a.x .. ", " .. a.y .. ")"
end
function Vector.new(x, y)
return setmetatable({ x = x or 0, y = y or 0 }, Vector)
end
function Vector.distance(a, b)
return (b - a):len()
end
function Vector:clone()
return Vector.new(self.x, self.y)
end
function Vector:unpack()
return self.x, self.y
end
function Vector:len()
return math.sqrt(self.x * self.x + self.y * self.y)
end
function Vector:lenSq()
return self.x * self.x + self.y * self.y
end
function Vector:normalize()
local len = self:len()
self.x = self.x / len
self.y = self.y / len
return self
end
function Vector:normalized()
return self / self:len()
end
function Vector:rotate(phi)
local c = math.cos(phi)
local s = math.sin(phi)
self.x = c * self.x - s * self.y
self.y = s * self.x + c * self.y
return self
end
function Vector:rotated(phi)
return self:clone():rotate(phi)
end
function Vector:perpendicular()
return Vector.new(-self.y, self.x)
end
function Vector:projectOn(other)
return (self * other) * other / other:lenSq()
end
function Vector:cross(other)
return self.x * other.y - self.y * other.x
end
setmetatable(Vector, { __call = function(_, ...) return Vector.new(...) end })
|
请发表评论