metatable是Lua中的重要概念,每個table均可以加上metatable,以改變相應的table的行爲。讓咱們看一個例子:git
t = {} -- 普通的table mt = {} -- metatable setmetatable(t, mt) -- 設定mt爲t的metatable getmetatable(t) -- 返回mt
使用getmetatable
和setmetatable
來查看和設定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
上面的代碼中使用了rawget
和rawset
以免死循環。使用這兩個函數,能夠避免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
設定很方便。例如,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 })