後設方法教學 |
|
Lua 有個強大的擴充機制,它能讓你重載 Lua 物件上的特定操作。每個重載物件都有個函式 *後設方法* 的 *後設表* 和它關聯;當適當時就會呼叫它們,類似於許多其他語言中的運算子重載概念。
後設表是一個包含一組後設方法的常規 Lua 表,會和 Lua 中的 *事件* 關聯。當 Lua 執行特定操作,例如加法、字串串接、比較等操作時,就會產生事件。後設方法是當特定事件發生時會被呼叫的常規 Lua 函式。事件有像 "add" 和 "concat" (見手冊第 2.8 節) 這樣的名稱,對應到後設表中像 "__add
" 和 "__concat
" 這樣的字串鍵。在這個範例中是新增 (+
) 或串接 (..
) 兩個 Lua 物件。
我們使用函式 setmetatable()
讓一個表對特定物件發揮後設表的效用。
local x = {value = 5} -- creating local table x containing one key,value of value,5 local mt = { __add = function (lhs, rhs) -- "add" event handler return { value = lhs.value + rhs.value } end } setmetatable(x, mt) -- use "mt" as the metatable for "x" local y = x + x print(y.value) --> 10 -- Note: print(y) will just give us the table code i.e table: <some tablecode> local z = y + y -- error, y doesn't have our metatable. this can be fixed by setting the metatable of the new object inside the metamethod
當加法運算子發現其運算元不是數字時,它會嘗試檢查其中一個是否有一個具有 __add 鍵的後設表。在這個案例中,它確實找到了,因此它會執行儲存在後設表中該鍵下的函式,等於以下程式碼
local y = (getmetatable(x).__add(x, x)) -- x + x
即使其中一個運算元是數字,後設表仍會由數學運算子觸發。左運算元永遠是函式的第一個參數,右運算元永遠是第二個參數。這表示擁有後設方法的表不一定是後設方法的第一個參數。
以下是 Lua 處理的其他後設方法事件的註解。要查看後設方法事件的完整清單,請參閱:MetatableEvents。
這是非常常用且多元的後設方法,當表中不存在鍵時,它會讓你執行自訂函式或使用「後備」表。如果使用函式,它的第一個參數會是查找失敗的表,第二個參數會是鍵。如果使用後備表,請記住如果它有 __index 後設方法,它可能會觸發 __index 後設方法,因此你可以建立長串的後備表。
local func_example = setmetatable({}, {__index = function (t, k) -- {} an empty table, and after the comma, a custom function failsafe return "key doesn't exist" end}) local fallback_tbl = setmetatable({ -- some keys and values present, together with a fallback failsafe foo = "bar", [123] = 456, }, {__index=func_example}) local fallback_example = setmetatable({}, {__index=fallback_tbl}) -- {} again an empty table, but this time with a fallback failsafe print(func_example[1]) --> key doesn't exist print(fallback_example.foo) --> bar print(fallback_example[123]) --> 456 print(fallback_example[456]) --> key doesn't exist
當你嘗試賦值給表中的某個鍵,而該鍵不存在(包含 nil)時,就會呼叫這個後設方法。如果鍵存在,則不會觸發後設方法。
local t = {} local m = setmetatable({}, {__newindex = function (table, key, value) t[key] = value end}) m[123] = 456 print(m[123]) --> nil print(t[123]) --> 456
當對兩個表使用 ==
運算子時,引發參考相等性檢查失敗,且兩個表都有同一個 __eq
後設方法(!)時,就會呼叫 __eq
。
__lt
會用來檢查一個物件是否「小於」另一個物件。和 __eq 不同的是,如果兩個物件有不同的 __lt
後設方法,並非錯誤,將會使用左邊物件的後設方法。
這是讓所有比較運算子都能和你物件運作所需的全部。不過會有一些狀況是 __lt
和 __eq
都必須被同個運算子所呼叫的。為了解決這個問題,你可以選擇性的增加 __le
(小於或等於) 函數。現在比較運算子呼叫時只會呼叫一個函數。
譬如說,我們可以改良本頁最上方
local mt mt = { __add = function (lhs, rhs) return setmetatable({value = lhs.value + rhs.value}, mt) end, __eq = function (lhs, rhs) return lhs.value == rhs.value end, __lt = function (lhs, rhs) return lhs.value < rhs.value end, __le = function (lhs, rhs) -- not really necessary, just improves "<=" and ">" performance return lhs.value <= rhs.value end, }
__metatable
用來保護 metatable。如果你不讓某個程式改變 metatable 的內容,你必須設定它的 __metatable 欄位。有了它,程式便無法讀取 metatable(因此也無法改變它)。
詳見 [1] 檢視所有函數清單和它們的功能說明。