【Lua 筆記】元表(MetaTable) - part 10
【Lua 筆記】元表(MetaTable) - part 10
由於有款遊戲叫做 CSO(Counter-Strike Online),內建模式創世者模式(Studio)新增使用 Lua 及其遊戲的 API,所以突發奇想製作這個筆記。
這個筆記會在一開始先著重純粹的程式設計自學,在最後的章節才會與 CSO 遊戲 API 進行應用。
元表(MetaTable)
在 Lua table 中我們可以存取到對應的 key 來得到 value 值,但是卻無法對兩個 table 進行運算操作(例如相加)。因此 Lua 提供了元表(MetaTable),允許我們改變 table 的行為,每個行為關聯了對應的元方法。
所以元表(MetaTable)可以讓我們針對 table 進行一些運算操作。
而在對 table 跟 table 之間進行運算的時候,Lua 首先會檢查兩者之間是否存在元表這個東西,例如會檢查是否有 __add 存在,找到的話,則其對應的值(往往是一個函數或是 table)就是”元方法”。
有兩個很重要的函數是專門用來處理元表的:
- setmetatable(table, metatable):對指定 table 設定元表(metatable),如果元表(metatable)中存在
__metatable鍵值,setmetatable 會失敗。 - getmetatable(table):回傳物件的元表(metatable)。
關於第一個最後面敘述講到為什麼會失敗,在這邊稍微解釋一下:
當我們用 setmetatable 為一個表設定元表時,如果提供的元表中包含 __metatable 鍵,Lua 會阻止這次操作並回傳錯誤。目的是保護元表不被隨意修改。(類似於不可變物件:immutable object)
以下是一個範例,有關於設置元表:
1 | mytable = {} -- table |
可直接寫成一行:
1 | mytable = setmetatable({},{}) |
__index 元方法
__index 是最常用的鍵。
當你透過鍵來存取 table 的時候,如果這個鍵沒有值,那麼 Lua 就會尋找該 table 的metatable(假設有 metatable)中的
__index鍵。如果__index包含一個表,Lua會在 table 中尋找對應的鍵。
什麼意思?如果該鍵在 table 中不存在(即沒有對應的值),Lua 不會立即回傳 nil,而是會檢查這個 table 是否有一個元表(metatable)。如果有,Lua 會在這個元表中尋找一個名為 __index 的鍵。
以下是來自菜鳥教程的範例(備註:以下範例是在 Shell 當中進行的,請注意):
1 | $ lua |
以下是筆者自創範例:
1 | local myTable = {name = "Programming Bot"} |
:::info
Line 27 : print(“Location: “ .. (myTable.location or “unknown”)) — Location: unknown:
(myTable.location or “unknown”):在 Lua 中,or 運算子會回傳左側運算元的值,如果左側運算元的值為 false 或 nil,則回傳右側運算元的值。所以如果 myTable.location 為 nil(即 location 鍵不存在或其值為 nil),則運算式的結果為 “unknown”。
:::
以下總結取自Lua 元表(Metatable) | 菜鸟教程,老實講寫的很好:
Lua 找出一個表中元素時的規則,其實就是如下 3 個步驟:
- 在表中查找,如果找到,回傳該元素,找不到則繼續。
- 判斷該表是否有元表,如果沒有元表,回傳 nil,有元表則繼續。
- 判斷元表有沒有
__index方法,如果__index方法為 nil,則回傳 nil;如果__index方法是一個表,則重複 1、2、3 步驟;如果__index 方法是一個函數,則回傳該函數的回傳值。
__newindex 元方法
__newindex 元方法用來對表進行更新,__index 則用來對表進行存取 。
當你給表的一個缺少的索引賦值,解釋器就會找
__newindex元方法:如果存在則呼叫這個函數而不進行賦值操作。
以下是一個範例:
1 | local myTable = {} |
輸出結果:
1 | 嘗試更新不存在的鍵:someKey,將其設定為:someKey |
:::info
rawset(table, key, value):
- table:要修改的表。
- key:要修改的鍵(索引)。
- value:要設定的值。
rawset 是 Lua 中的一個函數,它用於直接對 table 進行賦值運算,不觸發任何元方法(如__newindex)。這函數的作用是在特定情況下,允許我們直接繞過 Lua 的元方法機制,直接修改 table 的內容。
:::
運算型元方法
表格來源 Lua 元表(Metatable) | 菜鸟教程:
| 鍵 | 描述 |
|---|---|
__add | 對應運算子”+” |
__sub | 對應運算子”-“(做二元減法運算,兩數相減) |
__mul | 對應運算子”*” |
__div | 對應運算子”/“ |
__mod | 對應運算子”%” |
__unm | 對應運算子”-“(做一元減法運算,當作負號) |
__concat | 對應運算子 ‘..’ |
__eq | 對應運算子”==” |
__lt | 對應運算子”<” |
__le | 對應運算子”<=” |
以下是個範例:
1 | function table_maxn(t) |
__call 元方法
__call 元方法:允許定義一個 table 在被當作函數呼叫時的行為。表示能讓一個 table 像函數一樣被呼叫,並且可以自訂在被呼叫時應該做什麼。(總之就是讓表變成函數那般作用)
以下是一個範例(來自菜鳥教程):
1 | function table_maxn(t) |
輸出結果:
1 | 70 |
這支程式碼主要是用作於兩表之間的值進行相加。
__tostring 元方法
__tostring 元方法:用於定義當一個 table 被轉換為字串時的行為。總之就是修改 table 的輸出行為,__tostring 必定回傳字串。
以下是一個範例(來自菜鳥教程):
1 | mytable = setmetatable({ 10, 20, 30 }, { |
輸出結果:
1 | 表所有的元素和為 60 |
總結
元表(MetaTable)可以讓我們針對 table 進行一些運算操作。
而在對 table 跟 table 之間進行運算的時候,Lua 首先會檢查兩者之間是否存在元表這個東西,例如會檢查是否有 __add 存在,找到的話,則其對應的值(往往是一個函數或是 table)就是”元方法”。
有兩個很重要的函數是專門用來處理元表的:
- setmetatable(table, metatable):對指定 table 設定元表(metatable),如果元表(metatable)中存在
__metatable鍵值,setmetatable 會失敗。 - getmetatable(table):回傳物件的元表(metatable)。
關於第一個最後面敘述講到為什麼會失敗,在這邊稍微解釋一下:
當我們用 setmetatable 為一個表設定元表時,如果提供的元表中包含 __metatable 鍵,Lua 會阻止這次操作並回傳錯誤。目的是保護元表不被隨意修改。(類似於不可變物件:immutable object)
__index 是最常用的鍵。主要功用是用於 table 的查找(存取 table)跟指定預設值。
__newindex 用於對 table 進行更新。
運算型元方法:
| 鍵 | 描述 |
|---|---|
__add | 對應運算子”+” |
__sub | 對應運算子”-“(做二元減法運算,兩數相減) |
__mul | 對應運算子”*” |
__div | 對應運算子”/“ |
__mod | 對應運算子”%” |
__unm | 對應運算子”-“(做一元減法運算,當作負號) |
__concat | 對應運算子 ‘..’ |
__eq | 對應運算子”==” |
__lt | 對應運算子”<” |
__le | 對應運算子”<=” |
__call 將表變成函數使用。
__tostring 回傳值必須是字串。
參考資料
【30天Lua重拾筆記28】進階議題: Meta Programming | 又LAG隨性筆記



