深度理解Lua中的table元方法

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

這篇blog參考MetatableEvents响蓉,一個一個邊寫測試邊細(xì)說。

__newindex

原文翻譯:

__newindex用于分配屬性哨毁,當(dāng)調(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

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

__newindex方法中傳進(jìn)來的參數(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,這樣就進(jìn)入了死循環(huán)证九,導(dǎo)致棧溢出删豺。這時就需要用到方法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賦值拥坛,不會進(jìn)入__newindex
的方法蓬蝶。

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

__newindex的默認(rèn)值就是上面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)的繼承忆蚀,當(dāng)訪問 myTable[key] 時饮笛,如果myTable中不存在這個key,但是如果元表(metatable)中有 __index時:

  • 如果__index是一個function搀菩,傳遞的參數(shù)是tablekey,function的返回值作為結(jié)果返回螃成。
  • 如果__index是一個table旦签,就返回這個表中key對應(yīng)的值。
    • 如果這個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竿秆。當(dāng)執(zhí)行test.name時,如果test.name是nil稿壁,會調(diào)用__index的function幽钢,并返回function的返回值。否則傅是,直接返回test[key]匪燕,不會進(jìn)入__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,

    -- 默認(rèn)實現(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

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

__call

原文翻譯:

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

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

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

原文翻譯:

隱藏真正的元表硕淑,當(dāng)調(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。很有意思赃份,但是暫時沒想到有什么應(yīng)用場景寂拆。

__tostring

原文翻譯:

控制字符串的表現(xiàn),當(dāng)調(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的長度英上。當(dāng)用#操作符請求長度時炭序,且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對應(yīng)的值是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中所有的元方法就分析完了,還有一些操作符重載的牵舵,不細(xì)說了柒啤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市畸颅,隨后出現(xiàn)的幾起案子担巩,更是在濱河造成了極大的恐慌,老刑警劉巖没炒,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涛癌,死亡現(xiàn)場離奇詭異,居然都是意外死亡送火,警方通過查閱死者的電腦和手機(jī)拳话,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來种吸,“玉大人弃衍,你說我怎么就攤上這事〖崴祝” “怎么了镜盯?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猖败。 經(jīng)常有香客問我速缆,道長,這世上最難降的妖魔是什么恩闻? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任艺糜,我火速辦了婚禮,結(jié)果婚禮上幢尚,老公的妹妹穿的比我還像新娘破停。我一直安慰自己,他們只是感情好尉剩,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布辱挥。 她就那樣靜靜地躺著,像睡著了一般边涕。 火紅的嫁衣襯著肌膚如雪晤碘。 梳的紋絲不亂的頭發(fā)上褂微,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音园爷,去河邊找鬼宠蚂。 笑死,一個胖子當(dāng)著我的面吹牛童社,可吹牛的內(nèi)容都是我干的求厕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扰楼,長吁一口氣:“原來是場噩夢啊……” “哼呀癣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弦赖,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤项栏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹬竖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沼沈,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年币厕,在試婚紗的時候發(fā)現(xiàn)自己被綠了列另。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡旦装,死狀恐怖页衙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阴绢,我是刑警寧澤店乐,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站旱函,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏描滔。R本人自食惡果不足惜棒妨,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望含长。 院中可真熱鬧券腔,春花似錦、人聲如沸拘泞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陪腌。三九已至辱魁,卻和暖如春烟瞧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背染簇。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工参滴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锻弓。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓砾赔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親青灼。 傳聞我的和親對象是個殘疾皇子暴心,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

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