深度理解Lua中的table元方法

Lua的table是個很有意思的東西。有些內(nèi)容平時寫代碼的時候很少接觸到,但是了解一下還是很有意思的。

這篇blog參考MetatableEvents,一個一個邊寫測試邊細說。

__newindex

原文翻譯:

__newindex用于分配屬性,當調(diào)用 myTable[key]=value時,如果元表中有__newindex并且指向一個function,就會調(diào)用這個function,傳入的參數(shù)為table, key 和 value

  • rawset(myTable, key, value)可以跳過這個元方法直接給myTable的key屬性賦值為value。
  • 如果__newindex指向的方法中,沒有調(diào)用rawset方法,傳入的鍵值對(key/value)就不會添加到myTable中。

測試代碼:

local meta = {
    __newindex = function(t, key, value)
        print("call __newindex",t, key, value)
    end
}

local test = {}
setmetatable(test, meta)

print("test", test)
print("meta", meta)

test.name = "t1"
test.name = "t2"
print("test.name", test.name)

---- result output ----
test    table: 0x7f9c13406f00
meta    table: 0x7f9c13407240
call __newindex table: 0x7f9c13406f00   name    t1
call __newindex table: 0x7f9c13406f00   name    t2
test.name   nil

測試代碼中,當給t的name的賦值時,就會觸發(fā)元表中的__newindex指向的function,打印的信息可以看到key和value的值。

__newindex方法中傳進來的參數(shù)t的指針和test的指針指向同一個地址,說明__newindex中的參數(shù)t,并不是元表。

測試代碼中對t.name連續(xù)賦值時,__newindex會連續(xù)調(diào)用,需要留意一下這里,后面的測試會跟這里做一個對比。

賦值之后打印 t.name 的值是空的。原因是__newindex并沒有給t.name賦值,我們用一個錯誤的方式給t.name賦值,來加深__newindex的理解。修改一下測試代碼:

local meta = {
    __newindex = function(t, key, value)
        print("call __newindex",t, key, value)
        t[key] = value
    end
}

local test = {}
setmetatable(test, meta)

print("test", test)
print("meta", meta)

test.name = "t1"
test.name = "t2"
print(test.name)

---- result output ----
...
lua: C stack overflow
...

報錯信息,棧溢出。因為t[key] = value這段代碼會調(diào)用t元表中的__newindex的方法,__newindex的方法又會調(diào)用t[key] = value,這樣就進入了死循環(huán),導致棧溢出。這時就需要用到方法rawset

修改測試代碼:

local meta = {
    __newindex = function(t, key, value)
        print("call __newindex",t, key, value)
        rawset(t, key, value)
    end
}

local test = {}
setmetatable(test, meta)

test.name = "t1"
test.name = "t2"
print("test.name", test.name)

---- result output ----
call __newindex table: 0x7fdade404e20   name    t1
test.name   t2

這段代碼中信息比較多

__newindex中使用了rawset方法,可以看到,沒有棧溢出的錯誤了,說明用rawset給table賦值,不會進入__newindex
的方法。

給t.name連續(xù)賦值,會發(fā)現(xiàn)只進入__newindex一次,跟之前不同的是,我們在__newindex給t.name賦了值。如果t中沒有這個key時,才會進入__newindex方法。否則不會進入。

__newindex的默認值就是上面meta.__newindex的代碼。如果不需要額外處理,完全可以不寫。如下:

local meta = {}
local test = {}
setmetatable(test, meta)

test.name = "t1"
print("test.name", test.name)

---- result output ----
test.name   t1

__index

翻譯原文

__index用于控制屬性(prototype)的繼承,當訪問 myTable[key] 時,如果myTable中不存在這個key,但是如果元表(metatable)中有 __index時:

  • 如果__index是一個function,傳遞的參數(shù)是tablekey,function的返回值作為結(jié)果返回。
  • 如果__index是一個table,就返回這個表中key對應的值。
    • 如果這個table不存在該key,但是這個table有元表,會繼續(xù)尋找元表中的__index屬性,以此類推。都沒有就返回nil
  • 使用 "rawget(myTable,key)" 可以跳過這個元方法(__index).

寫點測試:

local test = {}

local meta = {
    __index = function(t, k)
        print("__index", k)
        if rawget(t, k) == nil then
            print("Can't find ".. k)
        end

        return rawget(t, k)
    end,
}

setmetatable(test, meta)

print("test.name1", test.name)
test.name = "hello"
print("test.name2", test.name)


---- result output ----

__index name
Can't find name
test.name1  nil
test.name2  hello

__newindex__index其實可以類比成setter和getter,這么類比會比較容易理解,但是實際上還是有比較大的區(qū)別。

上面的測試中__index是個function。當執(zhí)行test.name時,如果test.name是nil,會調(diào)用__index的function,并返回function的返回值。否則,直接返回test[key],不會進入__index。

再做一個測試,這次__index是個table

local test = {}

local meta = {
    __index = {name="meta"},
}

setmetatable(test, meta)

print("test.name1", test.name)
test.name = "hello"
print("test.name2", test.name)

---- result output ----

test.name1  meta
test.name2  hello

這個測試可以看到,訪問順序是先訪問test的name,如果沒有值,再訪問test元表中__index的table。如果test的元表還有元表,會繼續(xù)向上訪問,Lua繼承的實現(xiàn)就是利用這個特性。

掌握__newindex__index這兩個元方法,可以把這兩個元方法看做兩個事件,那要就要清楚兩個方法的觸發(fā)條件和特性。才能融會貫通。

舉個例子:

禁用全局變量

local meta = {
    __newindex = function(t, k, v)
        print("Error! Can't set globle variable", k)
    end,

    -- 默認實現(xiàn)
    -- __index = function(t, k)
    --     return rawget(t, k)
    -- end
}

setmetatable(_G, meta)

test = "test"
print(test)

---- result output ----

Error! Can't set globle variable    test
nil

__mode

原文翻譯:

控制弱引用,用字符kv來代表table的是否是弱引用。這個感覺沒什么好說的,只寫個測試就好了。

local meta = {__mode = "k"}
local test = {}
setmetatable(test, meta)
key = {}
test[key] = 1
key = {}
test[key] = 2
for k,v in pairs(test) do
    print(v)
end

collectgarbage()
print("collectgarbage")

for k,v in pairs(test) do
    print(v)
end

---- result output ----

1
2
collectgarbage
2

例子中當調(diào)用collectgarbage()進行回收后,test表中只剩下一個值。弱引用的key被清理了。我們也可以在__mode中設置v,kv來表示 鍵和值都是弱引用。

__call

原文翻譯:

把table當做一個function使用,當table后跟一個圓括號時,而且table的元表中的__call指向一個function,就會調(diào)用這個function,table自己做為第一個參數(shù),后面可接任意數(shù)量的參數(shù),返回值就是function的返回值。

測試代碼來模擬實現(xiàn)一個構造方法。

local meta = {
    __call = function(t, ...)

        local instance = {}
        for k, v in pairs(t) do
            instance[k] = v
        end
        return instance
    end
}

local A = setmetatable({}, meta)

function A:info()
    print("info",self)
end

local a = A()
local b = A()

a:info()
b:info()

---- result output ----
info    table: 0x7f8771e05030
info    table: 0x7f8771e050a0

__metatable

原文翻譯:

隱藏真正的元表,當調(diào)用getmetatable時,而且table的元表有__metatable字段,則返回__metatable字段中的值。

測試代碼:

local meta = {
    name = "meta"
}

local test = setmetatable({}, meta)
print(getmetatable(test).name)

local meta = {
    __metatable = {name = "__metatable"},
    name = "meta"
}

local test = setmetatable({}, meta)
print(getmetatable(test).name)

---- result output ----
meta
__metatable

結(jié)果很直觀不解釋了,我另外還做了個的測試,讓__metatable指向了一個function,調(diào)用getmetatable時也會返回這個function。很有意思,但是暫時沒想到有什么應用場景。

__tostring

原文翻譯:

控制字符串的表現(xiàn),當調(diào)用tostring(myTable)時,且myTable的元表中有__tostring字段時,就會調(diào)用這個方法。返回值是方法的返回值。

測試代碼:

local meta = {
    __tostring = function(t)
        return string.format("My name is %s", t.name)
    end
}

local test = setmetatable({}, meta)
test.name = "test"
print(test)
print(tostring(test))

---- result output ----
My name is test
My name is test

這個也不做過多說明了,很容易理解。有一點提一下就是print方法會自動調(diào)用tostring(test)

__len

原文翻譯:

控制table的長度。當用#操作符請求長度時,且table的元表有__len字段指向一個function,就會調(diào)用這個function,參數(shù)是table自己,返回值是function的返回值。

寫個測試代碼:

local meta = {
    __len = function(t)
        local result = 0
        for k, v in pairs(t) do
            result = result + 1
        end
        return result
    end
}


local test = {
    [1] = "A",
    [2] = "B",
    [3] = "C",
    [5] = "D",
    [6] = "E",
    [8] = "F",
}
print(#test)

setmetatable(test, meta)
print(#test)

---- result output ----
3
6

Lua中使用#獲取長度有個特性,就是如果某個key對應的值是nil就結(jié)束,上面例子中,test第4個值是nil,那返回的長度為3。我們重新定義了__len后返回了,用遍歷的方式計算長度,返回table內(nèi)元素的數(shù)量為6。

__gc

原文翻譯:

簡單說就是數(shù)據(jù)被垃圾回收的時候會首先觸發(fā)__gc。

測試代碼:

local meta = {

    __gc = function(t)
        print("gc")
    end
}

local function test()
    local test = {}
    setmetatable(test, meta)
end

test()

---- result output ----
gc

Lua table中所有的元方法就分析完了,還有一些操作符重載的,不細說了。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,235評論 0 38
  • MYSQL 基礎知識 1 MySQL數(shù)據(jù)庫概要 2 簡單MySQL環(huán)境 3 數(shù)據(jù)的存儲和獲取 4 MySQL基本操...
    Kingtester閱讀 8,050評論 5 115
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,295評論 0 17
  • 后生把山趕成一個圓圈之后,請風水先生來尋找帝王龍脈,果然找到了一個叫做“丹鳳朝陽”的帝王龍脈之地。據(jù)說就是...
    覃夢云閱讀 3,932評論 1 2
  • 今天是5-28號,通過3天高密度的《顧問式總裁》學習和1天的輔導,我的收獲是: 1.鞏固和了我這三天以來所學的知識...
    大馬哈魚_f300閱讀 509評論 0 2

友情鏈接更多精彩內(nèi)容