lua的元表

metatable是Lua中的重要概念,每個table均可以加上metatable,以改變相應的table的行爲。讓咱們看一個例子:git

t = {} -- 普通的table
mt = {} -- metatable
setmetatable(t, mt) -- 設定mt爲t的metatable
getmetatable(t) -- 返回mt

使用getmetatablesetmetatable來查看和設定metatable。固然,上面的代碼也能夠壓縮成一行:github

t = setmetatable({}, {})

這是由於setmetatable會返回它的第一個參數。函數

metatable能夠包括任何東西,metatable特有的鍵通常以__開頭,例如__index__newindex,它們的值通常是函數或其餘table。lua

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會在表格中查找相應的鍵。code

other = { foo = 3 }
t = setmetatable({}, { __index = other })
t.foo -- 3
t.bar -- nil

若是__index包含一個函數的話,Lua就會調用那個函數,table和鍵會做爲參數傳遞給函數。orm

__newindex

相似__index__newindex的值爲函數或table,用於按鍵賦值的狀況。ip

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

上面的代碼中使用了rawgetrawset以免死循環。使用這兩個函數,能夠避免Lua使用__index__newindex字符串

運算符

利用metatable能夠定義運算符,例如*get

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設定很方便。例如,kikitotween.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 })

原文 Lua Metatables Tutorial
翻譯 SegmentFault

相關文章
相關標籤/搜索