转载地址:http://segmentfault.com/a/1190000000410274
metatable是Lua中的重要概念,每一个table都可以加上metatable,以改变相应的table的行为。让我们看一个例子:
t = {} – 普通的table
mt = {} – metatable
setmetatable(t, mt) – 设定mt为t的metatable
getmetatable(t) – 返回mt
使用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 – 3
t.bar – nil
如果index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__newindex
类似index,newindex的值为函数或table,用于按键赋值的情况。
other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3
other.foo – 3
t.foo – nil
t = setmetatable({}, {
  __newindex = function(t, key, value)
    if type(value) == “number” then
      rawset(t, key, value * value)
    else
      rawset(t, key, value)
    end
  end
})
t.foo = “foo”
t.bar = 4
t.la = 10
t.foo – “foo”
t.bar – 16
t.la – 100
上面的代码中使用了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 – { 1, 2, 3, 1, 2, 3 }
和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) – 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 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 })