lua元表詳解


元表的作用

元表是用來定義對table或userdata操作方式的表

舉個例子

local t1 = {1}
local t2 = {2}
local t3 = t1 + t2

我們直接對兩個table執行+運算,會報錯

lua: /usercode/file.lua:3: attempt to perform arithmetic on local 't1' (a table value)

因為程序不知道如何對兩個表執行+運行,這時候就需要通過元表來定義如何執行t1的+運算,有點類似於c語言中的運算符重載。

local mt = {}
--定義mt.__add元方法(其實就是元表中一個特殊的索引值)為將兩個表的元素合並后返回一個新表
mt.__add = function(t1,t2)
	local temp = {}
	for _,v in pairs(t1) do
		table.insert(temp,v)
	end
	for _,v in pairs(t2) do
		table.insert(temp,v)
	end
	return temp
end
local t1 = {1,2,3}
local t2 = {2}
--設置t1的元表為mt
setmetatable(t1,mt)

local t3 = t1 + t2
--輸出t3
local st = "{"
for _,v in pairs(t3) do
	st = st..v..", "
end
st = st.."}"
print(st)

結果為:

{1, 2, 3, 2, }

因為程序在執行t1+t2的時候,會去調用t1的元表mt的__add元方法進行計算。
具體的過程是:
1.查看t1是否有元表,若有,則查看t1的元表是否有__add元方法,若有則調用。
2.查看t2是否有元表,若有,則查看t2的元表是否有__add元方法,若有則調用。
3.若都沒有則會報錯。
所以說,我們通過定義了t1元表的__add元方法,達到了讓兩個表通過+號來相加的效果

元表的元方法

函數 描述
__add 運算符 +
__sub 運算符 -
__mul 運算符 *
__ div 運算符 /
__mod 運算符 %
__unm 運算符 -(取反)
__concat 運算符 ..
__eq 運算符 ==
__lt 運算符 <
__le 運算符 <=
__call 當函數調用
__tostring 轉化為字符串
__index 調用一個索引
__newindex 給一個索引賦值

由於那幾個運算符使用類似,所以就不單獨說明了,接下來說 __call, __tostring, __index, __newindex四個元方法。

__call

__call可以讓table當做一個函數來使用。

local mt = {}
--__call的第一參數是表自己
mt.__call = function(mytable,...)
    --輸出所有參數
    for _,v in ipairs{...} do
        print(v)
    end
end

t = {}
setmetatable(t,mt)
--將t當作一個函數調用
t(1,2,3)

結果:

1
2
3

__tostring

__tostring可以修改table轉化為字符串的行為

local mt = {}
--參數是表自己
mt.__tostring = function(t)
    local s = "{"
    for i,v in ipairs(t) do
        if i > 1 then
            s = s..", "
        end
        s = s..v
    end
    s = s .."}"
    return s
end

t = {1,2,3}
--直接輸出t
print(t)
--將t的元表設為mt
setmetatable(t,mt)
--輸出t
print(t)

結果:

table: 0x14e2050
{1, 2, 3}

__index

調用table的一個不存在的索引時,會使用到元表的__index元方法,和前幾個元方法不同,__index可以是一個函數也可是一個table。
作為函數:
將表和索引作為參數傳入__index元方法,return一個返回值

local mt = {}
--第一個參數是表自己,第二個參數是調用的索引
mt.__index = function(t,key)
    return "it is "..key
end

t = {1,2,3}
--輸出未定義的key索引,輸出為nil
print(t.key)
setmetatable(t,mt)
--設置元表后輸出未定義的key索引,調用元表的__index函數,返回"it is key"輸出
print(t.key)

結果:

nil
it is key

作為table:
查找__index元方法表,若有該索引,則返回該索引對應的值,否則返回nil

local mt = {}
mt.__index = {key = "it is key"}

t = {1,2,3}
--輸出未定義的key索引,輸出為nil
print(t.key)
setmetatable(t,mt)
--輸出表中未定義,但元表的__index中定義的key索引時,輸出__index中的key索引值"it is key"
print(t.key)
--輸出表中未定義,但元表的__index中也未定義的值時,輸出為nil
print(t.key2)

結果:

nil
it is key
nil

__newindex

當為table中一個不存在的索引賦值時,會去調用元表中的__newindex元方法
作為函數
__newindex是一個函數時會將賦值語句中的表、索引、賦的值當作參數去調用。不對表進行改變

local mt = {}
--第一個參數時表自己,第二個參數是索引,第三個參數是賦的值
mt.__newindex = function(t,index,value)
    print("index is "..index)
    print("value is "..value)
end

t = {key = "it is key"}
setmetatable(t,mt)
--輸出表中已有索引key的值
print(t.key)
--為表中不存在的newKey索引賦值,調用了元表的__newIndex元方法,輸出了參數信息
t.newKey = 10
--表中的newKey索引值還是空,上面看着是一個賦值操作,其實只是調用了__newIndex元方法,並沒有對t中的元素進行改動
print(t.newKey)

結果:

it is key
index is newKey
value is 10
nil

作為table
__newindex是一個table時,為t中不存在的索引賦值會將該索引和值賦到__newindex所指向的表中,不對原來的表進行改變。

local mt = {}
--將__newindex元方法設置為一個空表newTable
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--對t中不存在的索引進行負值時,由於t的元表中的__newindex元方法指向了一個表,所以並沒有對t中的索引進行賦值操作將,而是將__newindex所指向的newTable的newKey索引賦值為了"it is newKey"
t.newKey = "it is newKey"
print(t.newKey,newTable.newKey)

結果:

nil	nil
nil	it is newKey

rawget 和 rawset

有時候我們希望直接改動或獲取表中的值時,就需要rawget和rawset方法了。
rawget可以讓你直接獲取到表中索引的實際值,而不通過元表的__index元方法。

local mt = {}
mt.__index = {key = "it is key"}
t = {}
setmetatable(t,mt)
print(t.key)
--通過rawget直接獲取t中的key索引
print(rawget(t,"key"))

結果:

it is key
nil

rawset可以讓你直接為表中索引的賦值,而不通過元表的__newindex元方法。

local mt = {}
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--通過rawset直接向t的newKey索引賦值
rawset(t,"newKey","it is newKey")
print(t.newKey,newTable.newKey)

結果:

nil	nil
it is newKey	nil

元表的使用場景

作為table的元表

通過為table設置元表可以在lua中實現面向對象編程。

作為userdata的元表

通過對userdata和元表可以實現在lua中對c中的結構進行面向對象式的訪問。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM