接上文
元表
Q:為什么要使用元表?
A:在Lua中,常常會需要表與表之間的操作。元表中提供了一些元方法,通過自定義元方法可以實現(xiàn)想要的功能,相當于面向?qū)ο笾薪o你一系列方法讓你重載。
使用下列代碼設置元表:
meta={}
table={}
setmetatable(table,meta) -- 第一個元素為子表,第二個元素為元表
通常在元表中操作分為三步:
- 操作子表
- 檢測是否有元表
- 若有元表,檢測有無對應元方法,沒有元方法則返回對應操作本來的處理。若有對應元方法執(zhí)行元方法。
元方法索引一般以"__"(兩個下劃線,我想這是因為命名私有靜態(tài)變量常常以一個下劃線作為開頭)作為開頭,例如下面的例子:
__tostring
meta = {}
table={}
setmetatable(table, meta)
print(table)
輸出:
table: 00ABA2A8
meta = {
__tostring = function () <--注意兩個下劃線
return "123"
end
}
table={}
setmetatable(table, meta)
print(table)
輸出:
123
上例相當于使用元方法重載print函數(shù)
meta = {
__tostring = function (t)
return t.name
end
}
table={name = "233"}
setmetatable(table, meta)
print(table)
輸出:
233
在上例中,即使我們未指定元方法的入?yún)?,但是因為子表和元表的關(guān)聯(lián),元方法會自動地將子表作為參數(shù)傳入元方法。
__call
讓我們再定義一個__call
元方法
meta = {
__tostring = function ()
return "456"
end,
__call = function(a)
print(a.name)
print(a)
print("call")
end
}
table={name = "123"}
setmetatable(table, meta)
table(2) --無論table(x)中給出的x為幾,結(jié)果都是一樣的
輸出:
123
456
call
定義了__call
元方法后,我們使用table(index)
,發(fā)現(xiàn)__call
元方法打印了123,456和call,123是子表的元素,而456是__tostring
的返回結(jié)果。這意味著__call
方法調(diào)用了子表作為入?yún)。并且像我們上面的打印樣例一樣,print(a)
方法又同時會調(diào)用__tostring
的元方法。而table(2)
中的參數(shù)2很明顯被無視了。
現(xiàn)在我們可以確定一個基本規(guī)則:當使用__tostring
和__call
元方法時,我們定義的第一個參數(shù)一定是子表table本身,只有定義更多參數(shù)才能接收其它入?yún)ⅲ?/strong>
meta = {
__tostring = function ()
return "456"
end,
__call = function(a,b)
print(a)
print(b)
print("call")
end
}
table={name = "123"}
setmetatable(table, meta)
table(2)
輸出:
456
2
call
__index
meta = {
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
輸出:
nil
我們想要找到表中的name索引,當然是沒有的,輸出結(jié)果是nil
那能不能讓元表擁有這個索引呢?答案是不行,因為找的還是table1:
meta = {name =1}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
輸出:
nil
現(xiàn)在我們定義一個元方法 __index
,它會指向一個其他的查詢表
meta = { name = 2 }
meta.__index = meta
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
輸出:
2
整個查找的流程其實是:
- 查詢子表
- 子表查詢失敗,查詢元表有無
__index
元方法 - 若有,則查詢
__index
元方法指向的表
但是還有種情況,建議不要把__index
元方法在元表內(nèi)部定義:
meta = {
name = 2,
__index = meta,
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
輸出:
nil --不明覺厲
還可以套娃
meta = {
}
metaFather = {
name =4,
}
table1 ={age =1}
meta.__index = meta --讓元表的index指向meta,子表找不到就去meta里找
metaFather.__index =metaFather --讓元表的index指向metaFather,子表找不到就去metaFather里找
setmetatable(meta,metaFather)
setmetatable(table1, meta)
print(table1.name)
輸出:i
4 --允許套娃
__newindex
看4個例子:
meta = {}
table1 ={}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
輸出:
1
meta = {}
table1 ={age = 1}
meta.__newindex = {}
setmetatable(table1, meta)
print(table1.age)
輸出:
1
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age = 1
print(table1.age)
輸出:
nil
meta = {}
table1 ={}
meta.__newindex = {}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
輸出:
1
有沒有很詭異?其實很好理解,這是由于__newindex
元方法,它的作用其實是將子表新加入的元素加入到__newindex
所指向的表而不修改子表(當然__newindex
也可以套娃):
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
print(meta.__newindex.age)
輸出:
nil
1
運算符元方法
meta = {
__sub = function (t1,t2)
return t1.age - t2.age
end
}
table1={age=1}
table2={age=2}
setmetatable(table1, meta) --無論把元表設置給table1還是table2,結(jié)果都一樣
print(table1 - table2)
輸出:
-1
我們發(fā)現(xiàn)使用運算符元方法的時候,第一個參數(shù)也不默認是綁定的子表了。而是根據(jù)運算符的左右變量依次給元方法賦值。而且無論元表設置給減數(shù)還是被減數(shù),最終結(jié)果都是不變的。運算符元方法較多,這里就不詳細列舉了,從菜鳥教程上直接抄了個表格:
元方法 | 描述 |
---|---|
__add | 對應的運算符 ‘+’ |
__sub | 對應的運算符 ‘-’ |
__mul | 對應的運算符 ‘*’ |
__div | 對應的運算符 ‘/’ |
__mod | 對應的運算符 ‘%’ |
__unm | 對應的運算符 ‘-’ |
__concat | 對應的運算符 ‘..’ |
__pow | 對應的運算符 ‘^’ |
__eq | 對應的運算符 ‘==’ |
__lt | 對應的運算符 ‘<’ |
__le | 對應的運算符 ‘<=’ |
在這之中除了正常的數(shù)學運算,我們還需要講一下比較特殊的邏輯判斷的元方法(就是上表中加粗的幾列),這幾個元方法需要運算符的雙方都綁定同一個元表。原因是元方法沒有提供大于符號,運算a>b的元方法其實相當于運算b<a的原方法,這就需要b也綁定元表,因此邏輯運算硬性要求運算雙方都綁定同一個元表,讓我們看看下列的例子:
meta = {
__eq= function (t1,t2)
return true
end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
print(table1 == table2)
setmetatable(table2, meta)
print(table1 == table2)
輸出:
false
true
我們可以發(fā)現(xiàn)上述的相等的邏輯判斷是有問題的,第一個print出false,第二個print出true。
在第一個print的時候,只有table1
綁定了元表,而table2
沒有,之所以return false
是因為在邏輯運算的時候需要左右雙方都綁定同一個元表。
而在第二個print的時候打印了true
,即使table1
和table2
內(nèi)容不同。因為這是我們自定義的元方法,我們默認返回的就是true
。
幾道例題,請思考下列幾個例子的輸出結(jié)果:
meta = {
__eq= function (t1,t2)
if t1.age == t2.age then
return true
end
end,
}
table1 = { age = 1 }
table2 = { age = 2 }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
輸出:
false
上述例子中,我們用__eq
元方法來判斷age
索引值是否相同,顯然是不同的,因此是false
meta = {
__eq= function (t1,t2)
if t1.name == t2.name then
return true
end
end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
輸出:
true
上述例子中,我們用元方法判斷name
索引值是否相同,而table1沒有name
索引,默認為nil
,table2有name = nil
,因此是true
文章來源:http://www.zghlxwxcb.cn/news/detail-617369.html
meta = {
__eq= function (t1,t2)
if t1 == t2 then
return true
end
end,
}
table1 = { age = 1 }
table2 = { name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
輸出:
棧溢出
上述例子中,我們在元方法內(nèi)部判斷t1==t2
,而進行判斷時相當于再次調(diào)用了__eq
元方法,因此會無限循環(huán)最終導致棧溢出。文章來源地址http://www.zghlxwxcb.cn/news/detail-617369.html
其它元表操作
print(getmetatable(table1)) --使用getmetatable方法獲得table1的元表地址
輸出:
table: 00BBA320
meta = { name = 2 }
meta.__index = meta
table1 = { age = 1 }
setmetatable(table1, meta)
print(rawget(table1,"nane")) --rawget方法只查詢子表,無視元表index
輸出:
nil
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
rawset(table1, "age", 1) --rawset方法可以無視元表的newindex,直接修改子表
print(table1.age)
輸出:
nil
1
到了這里,關(guān)于【Lua學習筆記】Lua進階——Table(3) 元表的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!