lua中的變量是沒有數據類型的,值有類型。類型有八種nil,number,boolean, string, function, thread, userdata以及table。函數
Lua 中的每一個值均可以有一個 元表 。 這個 元表 就是一個普通的 Lua 表,它用於定義原始值在特定操做下的行爲。例如,當你對非數字值作加操做時, Lua 會檢查該值的元表中的 "__add" 域下的函數。 若是能找到,Lua 則調用這個函數來完成加這個操做。lua
元表中的鍵對應着不一樣的 事件 名; 鍵關聯的那些值被稱爲 元方法 。 在上面那個例子中引用的事件爲 "add" , 完成加操做的那個函數就是元方法。spa
能夠用 getmetatable 函數 來獲取任何值的元表。調試
使用 setmetatable 來替換一張表的元表。在 Lua 中,你不能夠改變表之外其它類型的值的元表 (除非你使用調試庫); 若想改變這些非表類型的值的元表,請使用 C API.code
接下來會給出一張元表能夠控制的事件的完整列表。 每一個操做都用對應的事件名來區分。 每一個事件的鍵名用加有 '__' 前綴的字符串來表示; 例如:"add" 操做的鍵名爲字符串 "__add"。對象
"add": + 操做。 若是任何不是數字的值(包括不能轉換爲數字的字符串)作加法, Lua 就會嘗試調用元方法。 首先、Lua 檢查第一個操做數(即便它是合法的), 若是這個操做數沒有爲 "__add" 事件定義元方法, Lua 就會接着檢查第二個操做數。 一旦 Lua 找到了元方法, 它將把兩個操做數做爲參數傳入元方法, 元方法的結果(調整爲單個值)做爲這個操做的結果。 若是找不到元方法,將拋出一個錯誤。
"sub": - 操做。 行爲和 "add" 操做相似。
"mul": * 操做。 行爲和 "add" 操做相似。
"div": / 操做。 行爲和 "add" 操做相似。
"mod": % 操做。 行爲和 "add" 操做相似。
"pow": ^ (次方)操做。 行爲和 "add" 操做相似。
"unm": - (取負)操做。 行爲和 "add" 操做相似。
"idiv": // (向下取整除法)操做。 行爲和 "add" 操做相似。
"band": & (按位與)操做。 行爲和 "add" 操做相似, 不一樣的是 Lua會在任何一個操做數沒法轉換爲整數時嘗試取元方法。
"bor": | (按位或)操做。 行爲和 "band" 操做相似。
"bxor": ~ (按位異或)操做。 行爲和 "band" 操做相似
"bnot": ~ (按位非)操做。 行爲和 "band" 操做相似。
"shl": << (左移)操做。 行爲和 "band" 操做相似。
"shr": >> (右移)操做。 行爲和 "band" 操做相似。
"concat": .. (鏈接)操做。 行爲和 "add" 操做相似, 不一樣的是 Lua在任何操做數即不是一個字符串 也不是數字(數字總能轉換爲對應的字符串)的狀況下嘗試元方法。
"len": # (取長度)操做。 若是對象不是字符串,Lua 會嘗試它的元方法。 若是有元方法,則調用它並將對象以參數形式傳入, 而返回值(被調整爲單個)則做爲結果。 若是對象是一張表且沒有元方法,Lua 使用表的取長度操做。 其它狀況,均拋出錯誤。
"eq": == (等於)操做。 和 "add" 操做行爲相似, 不一樣的是 Lua 僅在兩個值都是表或都是徹底用戶數據 且它們不是同一個對象時才嘗試元方法。 調用的結果總會被轉換爲布爾量。
"lt": < (小於)操做。 和 "add" 操做行爲相似, 不一樣的是 Lua 僅在兩個值不全爲整數也不全爲字符串時才嘗試元方法。 調用的結果總會被轉換爲布爾量。
"le": <= (小於等於)操做。 和其它操做不一樣, 小於等於操做可能用到兩個不一樣的事件。 首先,像 "lt" 操做的行爲那樣,Lua 在兩個操做數中查找 "__le" 元方法。 若是一個元方法都找不到,就會再次查找 "__lt" 事件, 它會假設 a <= b 等價於 not (b < a)。 而其它比較操做符相似,其結果會被轉換爲布爾量。blog
上面的這些元方法原理上均可以歸爲一類,咱們舉個例子來看:索引
local mt = {} mt.__add = function(a,b) return a.v + b.v/2 end local a = {v = 10} local b = {v = 12} setmetatable(a, mt) print("a + b :", a + b)
運行結果以下:事件
至關於重載了「+」號。內存
"index": 索引 table[key]。 當 table 不是表或是表 table 中不存在key 這個鍵時,這個事件被觸發。 此時,會讀出 table 相應的元方法。儘管名字取成這樣, 這個事件的元方法其實能夠是一個函數也能夠是一張表。 若是它是一個函數,則以 table 和 key 做爲參數調用它。若是它是一張表,最終的結果就是以 key 取索引這張表的結果。(這個索引過程是走常規的流程,而不是直接索引, 因此此次索引有可能引起另外一次元方法。
"newindex": 索引賦值 table[key] = value 。 和索引事件相似,它發生在 table 不是表或是表 table 中不存在 key 這個鍵的時候。 此時,會讀出 table 相應的元方法。同索引過程那樣, 這個事件的元方法便可以是函數,也能夠是一張表。 若是是一個函數, 則以 table、 key、以及 value 爲參數傳入。若是是一張表, Lua 對這張表作索引賦值操做。 (這個索引過程是走常規的流程,而不是直接索引賦值, 因此此次索引賦值有可能引起另外一次元方法。)一旦有了 "newindex" 元方法, Lua 就再也不作最初的賦值操做。(若是有必要,在元方法內部能夠調用 rawset 來作賦值。
經過一個例子來講明其原理:
local mt = {} mt.__index = function(t, k) print("call index function") end mt.__newindex = function(t,k,v) print("call new index function") end local t = {} setmetatable(t, mt) local temp = t.a t.a = 10
輸出以下:
也就是說,當調用表"t"中的元素"a",卻在表"t"中找不到"a"的時候,會把「t」,「a」作爲參數來調用「__index」函數。
當賦值表"t"中的元素"a",卻在表"t"中找不到"a"的時候,會把「t」,「a」,value作爲參數來調用「__newindex」函數。
"call": 函數調用操做 func(args)。 當 Lua 嘗試調用一個非函數的值的時候會觸發這個事件 (即 func 不是一個函數)。 查找 func 的元方法, 若是找獲得,就調用這個元方法, func 做爲第一個參數傳入,原來調用的參數(args)後依次排在後面。
經過一個例子來講明其原理:
local mt = {} mt.__call = function(f,...) print("call table like a function", f ,...) end local temp = {} setmetatable(temp, mt) temp(1,2,3)
輸出以下:
至關於把函數調用賦予到了一個非函數類型。
這個元方法比較實用。能夠把事件改變成表,能夠事先賦予事件上值。還能夠當成一個類的構造函數實使用。
此外還有一些元方法,書中並無歸類到這些事件裏面。我作了一些統計
"mode":弱表屬性,賦予一張表弱引用屬性。(弱表比較有趣,下一篇文章會作說明)
"gc":在對象被GC的時候,會先調用元表裏面的「gc」域。
"tostring":當調用tostring(obj)的時候,會先查找obj的元方法中的__tostring,若是有就調用,沒有就會打印obj的內存位置。好比說上面的。
"pairs":迭代器的元方法,在執行pairs(t)的時候,會先找表t的元方法「__pairs」,若是有就以t爲參數調用他,若是沒有,就返回三個值next函數, t已經nil。