Lua 類別和元表 |
|
Lua 已從應用程式延伸語言轉變成極具彈性的指令碼語言。Lua 5.0 不像 Java 或 Ruby 是個物件導向語言。相反地,Lua 讓你可以隨意實作類別。這是一大優點,也是一大缺點。超級使用者喜歡這種自由度,但新手有時會感到困惑。
元表是 Lua 為資料表加入魔法的方式。假設 t
是一個標準的資料表,如下所示
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = { 11, 22, 33, you='one', me='two' } > table.foreach(t,print) 1 11 2 22 3 33 me two you one > > = t[2] 22 > = t.me two > = t.fred nil
使用有效索引對資料表建立索引會傳回儲存在該索引的值。
使用未定義索引對資料表建立索引會傳回 nil
。如果加上點魔法,我們可以嘗試建立索引到另一個資料表,而不是傳回 nil
。我們甚至可以提供自己的函式來處理未定義的索引。
這些用於自訂特定 Lua 行為的函式在 Lua 早期版本稱為 *後備解決方案*。在 Lua 4.0 中,它們稱為 *標籤方法*。現在在 Lua 5.0 中(很大程度要感謝 Edgar Toernig),這些函式稱為 *後設方法*,而且儲存在稱為 *元表* 的資料表中。
我們可以自訂的行為有特殊名稱,而且會稱為 *事件*。將新索引加入資料表稱為 *newindex* 事件。嘗試從資料表讀取未定義的索引稱為 *index* 事件。
要查看存取未定義索引時發生什麼事,讓我們列印傳遞給用於 *index* 事件的後設方法的引數。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = { 11, 22, 33, you='one', me='two' } > mt = { __index = print } > = t.you one > = t.fred nil > setmetatable(t, mt) > x = t.fred table: 0x8075e80 fred > = x nil > = t table: 0x8075e80 >
請注意第一個引數是資料表 t
,而第二個引數是索引 fred
。
如果對 *newindex* 事件執行相同的操作,我們會看到有第三個引數,也就是儲存在索引中的新值。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = { 11, 22, 33, you='one', me='two' } > mt = { __newindex = print } > setmetatable(t, mt) > t[4] = 'rat' table: 0x8075e80 4 rat >
如前所述,我們可以指定資料表而不是函式,而且會存取該資料表。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = { 11, 22, 33, you='one', me='two' } > s = { } > mt = { __newindex = s, __index = _G } > setmetatable(t, mt) > = t.you one > x = 'wow' > = t.x wow > t[5] = 99 > table.foreach(s, print) 5 99 >
以下說明如何實作向量類別。我們有一個方法資料表和一個元表。每個物件都有另外一個資料表。所有物件共享同一個方法資料表和同一個元表。
請記住 v1:mag()
就像是 v1.mag(v1)
,因此 Lua 會嘗試在 v1
中查詢 mag
,這將會觸發 index
事件,然後在資料表 Vector
中查詢 mag
。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > Vector = {} > Vector_mt = { __index = Vector } > > function Vector:new(x,y) >> return setmetatable( {x=x, y=y}, Vector_mt) >> end > > function Vector:mag() >> return math.sqrt(self:dot(self)) >> end > > function Vector:dot(v) >> return self.x * v.x + self.y * v.y >> end > > v1 = Vector:new(3,4) > table.foreach(v1,print) y 4 x 3 > = v1:mag() 5 > v2 = Vector:new(2,1) > = v2:dot(v1) 10 > > = Vector table: 0x8076028 > table.foreach(Vector,print) mag function: 0x8078008 dot function: 0x8078b58 new function: 0x80773e8 > = v1, v2 table: 0x8079110 table: 0x8079a80 > = Vector_mt, getmetatable(v1), getmetatable(v2) table: 0x80763b8 table: 0x80763b8 table: 0x80763b8 > table.foreach(Vector_mt,print) __index table: 0x8076028 >
如果你想要一個預設建構函式和一個複製建構函式,你可以建立名為 Class.lua
的檔案,如下所示
function Class(members) members = members or {} local mt = { __metatable = members; __index = members; } local function new(_, init) return setmetatable(init or {}, mt) end local function copy(obj, ...) local newobj = obj:new(unpack(arg)) for n,v in pairs(obj) do newobj[n] = v end return newobj end members.new = members.new or new members.copy = members.copy or copy return mt end
然後將我們的 Vector 類別放入名為 Vec.lua
的檔案
require'Class' Vector = {} local Vector_mt = Class(Vector) function Vector:new(x,y) return setmetatable( {x=x, y=y}, Vector_mt) end function Vector:mag() return math.sqrt(self:dot(self)) end function Vector:dot(v) return self.x * v.x + self.y * v.y end
然後如下進行測試
$ lua -lVec -i -v Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > v1 = Vector:new(3,4) > table.foreach(v1,print) y 4 x 3 > = v1:mag() 5 > v2 = Vector:new(2,1) > = v2:dot(v1) 10 > > table.foreach(Vector,print) copy function: 0x80692c0 dot function: 0x8069300 mag function: 0x80692e0 new function: 0x8069398 > > v3 = v1:copy() > = v1, v2, v3 table: 0x80779d0 table: 0x8078428 table: 0x807a050 > table.foreach(v1,print) y 4 x 3 > table.foreach(v3,print) y 4 x 3 >
如果將 Class
函式套用至 Lua 的 table lib,我們可以建立資料表物件。
require'Class' Class(table) function table:push(x) assert( x ~= nil, 'will not push nil into table') self:insert(x) return self, x end function table:map(func, ...) local R = table:new{} for name,value in pairs(self) do func(R,name,value,unpack(arg)) end return R end function table:imap(func, ...) local R = table:new{} for index,elem in ipairs(self) do func(R,index,elem,unpack(arg)) end return R end
然後你不再需要輸入table.foreach
或table.getn(t)
。
$ lua -lTable -i -v Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = table:new{ 11, 22, 33, you='one', me='two' } > = t:getn() 3 > t:foreach(print) 1 11 2 22 3 33 me two you one > > = t:concat',' 11,22,33 > = table table: 0x8067808 > = getmetatable(t) table: 0x8067808 > > s = t:copy() > s:foreach(print) 1 11 2 22 3 33 me two you one > = s, t table: 0x8079a58 table: 0x8077bb8 >