1. 程式人生 > >Step By Step(Lua元表與元方法)

Step By Step(Lua元表與元方法)

 Lua中提供的元表是用於幫助Lua資料變數完成某些非預定義功能的個性化行為,如兩個table的相加。假設a和b都是table,通過元表可以定義如何計算表示式a+b。當Lua試圖將兩個table相加時,它會先檢查兩者之一是否有元表,然後檢查該元表中是否存在__add欄位,如果有,就呼叫該欄位對應的值。這個值就是所謂的“元方法”,這個函式用於計算table的和。
    Lua中每個值都有一個元表。table和userdata可以有各自獨立的元表,而其它資料型別的值則共享其型別所屬的單一元表。預設情況下,table在建立時沒有元表,如:
    t = {}
    print(getmetatable(t))  --輸出為nil


    這裡我們可以使用setmetatable函式來設定或修改任何table的元表。
    t1 = {}
    setmetatable(t,t1)
    assert(getmetatable(t) == t1)
    任何table都可以作為任何值的元表,而一組相關的table也可以共享一個通用的元表,此元表將描述了它們共同的行為。一個table甚至可以作為它自己的元表,用於描述其特有的行為。在Lua程式碼中,只能設定table的元表,若要設定其它型別值的元表,則必須通過C程式碼來完成。

    1. 算術類的元方法:
    在下面的示例程式碼中,將用table來表示集合,並且有一些函式用來計算集合的並集和交集等。

複製程式碼
 1 Set = {}
 2 local metatable = {} --元表 3 
 4 --根據引數列表中的值建立一個新的集合 5 function Set.new(l)
 6     local set = {}
 7     --將所有由該方法建立的集合的元表都指定到metatable 8     setmetatable(set,metatable)
 9     for _, v in ipairs(l) do
10         set[v] = true
11     end
12     return set
13 end
14 
15 --取兩個集合並集的函式16 function
Set.union(a,b) 17 local res = Set.new{} 18 for k in pairs(a) do 19 res[k] = true 20 end 21 for k in pairs(b) do 22 res[k] = true 23 end 24 return res 25 end 26 27 --取兩個集合交集的函式28 function Set.intersection(a,b) 29 local res = Set.new{} 30 for k in pairs(a) do 31 res[k] = b[k] 32 end 33 return res 34 end 35 36 function Set.tostring(set) 37 local l = {} 38 for e in pairs(set) do 39 l[#l + 1] = e 40 end 41 return "{" .. table.concat(l,", ") .. "}"; 42 end 43 44 function Set.print(s) 45 print(Set.tostring(s)) 46 end 47 48 --最後將元方法加入到元表中,這樣當兩個由Set.new方法創建出來的集合進行49 --加運算時,將被重定向到Set.union方法,乘法運算將被重定向到Set.intersection50 metatable.__add = Set.union 51 metatable.__mul = Set.intersection 52 53 --下面為測試程式碼54 s1 = Set.new{10,20,30,50} 55 s2 = Set.new{30,1} 56 s3 = s1 + s2 57 Set.print(s3) 58 Set.print(s3 * s1) 59 60 --輸出結果為:61 --{1, 30, 10, 50, 20}62 --{30, 10, 50, 20}
複製程式碼

    在元表中,每種算術操作符都有對應的欄位名,除了上述的__add(加法)__mul(乘法)外,還有__sub(減法)__div(除法)__unm(相反數)__mod(取模)__pow(乘冪)。此外,還可以定義__concat欄位,用於描述連線操作符的行為。
    對於上面的示例程式碼,我們在算術運算子的兩側均使用了table型別的運算元。那麼如果為s1 = s1 + 8,Lua是否還能正常工作呢?答案是肯定的,因為Lua定位元表的步驟為,如果第一個值有元表,且存在__add欄位,那麼Lua將以這個欄位為元方法,否則會再去檢視第二個值否是有元表且包含__add欄位,如果有則以此欄位為元方法。最後,如果兩個值均不存在元方法,Lua就引發一個錯誤。然而對於上例中的Set.union函式,如果執行s1 = s1 + 8將會引發一個錯誤,因為8不是table物件,不能基於它執行pairs方法呼叫。為了得到更準確的錯誤資訊,我們需要給Set.union函式做如下的修改,如:

複製程式碼
1 function Set.union(a,b)
2     if getmetatable(a) ~= metatable or getmetatable(b) ~= metatable then
3         error("attempt to 'add' a set with a non-set value")
4     end
5     --後面的程式碼與上例相同。6     ... ...
7 end
複製程式碼


    2. 關係類的元方法:
    元表還可以指定關係操作符的含義,元方法分別為__eq(等於)、__lt(小於)和__le(小於等於),至於另外3個關係操作符,Lua沒有提供相關的元方法,可以通過前面3個關係運算符的取反獲得。見如下示例:

複製程式碼
 1 Set = {}
 2 local metatable = {}
 3 
 4 function Set.new(l)
 5     local set = {}
 6     setmetatable(set,metatable)
 7     for _, v in ipairs(l) do
 8         set[v] = true
 9     end
10     return set
11 end
12 
13 metatable.__le = function(a,b) 
14     for k in pairs(a) do
15         if not b[k] then return false end
16     end
17     return true
18 end
19 metatable.__lt = function(a,b) return a <= b and not (b <= a) end
20 metatable.__eq = function(a,b) return a <= b and b <= a end
21 
22 --下面是測試程式碼:23 s1 = Set.new{2,4}
24 s2 = Set.new{4,10,2}
25 print(s1 <= s2) --true26 print(s1 < s2)  --true27 print(s1 >= s1) --true28 print(s1 > s1)  --false
複製程式碼

    與算術類的元方法不同,關係類的元方法不能應用於混合的型別。

    3. 庫定義的元方法:
    除了上述基於操作符的元方法外,Lua還提供了一些針對框架的元方法,如print函式總是呼叫tostring來格式化其輸出。如果當前物件存在__tostring元方法時,tostring將用該元方法的返回值作為自己的返回值,如:

複製程式碼
 1 Set = {}
 2 local metatable = {}
 3 
 4 function Set.new(l)
 5     local set = {}
 6     setmetatable(set,metatable)
 7     for _, v in ipairs(l) do
 8         set[v] = true
 9     end
10     return set
11 end
12 
13 function Set.tostring(set)
14     local l = {}
15     for e in pairs(set) do
16         l[#l + 1] = e
17     end
18     return "{" .. table.concat(l,", ") .. "}";
19 end
20  
21 metatable.__tostring = Set.tostring
22 
23 
24 --下面是測試程式碼:25 s1 = Set.new{4,5,10}
26 print(s1) --{5,10,4}
複製程式碼

    函式setmetatable和getmetatable也會用到元表中的一個欄位(__metatable),用於保護元表,如:

1 mt.__metatable = "not your business"
2 s1 = Set.new{}
3 print(getmetatable(s1))   --此時將列印"not your business"4 setmetatable(s1,{})  --此時將輸出錯誤資訊:"cannot change protected metatable"

    從上述程式碼的輸出結果即可看出,一旦設定了__metatable欄位,getmetatable就會返回這個欄位的值,而setmetatable將引發一個錯誤。

    4. table訪問的元方法:
    算術類和關係類運算子的元方法都為各種錯誤情況定義了行為,它們不會改變語言的常規行為。但是Lua還提供了一種可以改變table行為的方法。有兩種可以改變的table行為:查詢table及修改table中不存在的欄位。
    
    1). __index元方法:
    當訪問table中不存在的欄位時,得到的結果為nil。如果我們為該table定義了元方法__index,那個訪問的結果將由該方法決定。見如下示例程式碼:

複製程式碼
 1 Window = {} 
 2 Window.prototype = {x = 0, y = 0, width = 100, height = 100}
 3 Window.mt = {}  --Window的元表 4 
 5 function Window.new(o)
 6     setmetatable(o,Window.mt)
 7     return o
 8 end
 9 
10 --將Window的元方法__index指向一個匿名函式11 --匿名函式的引數table和key取自於table.key。12 Window.mt.__index = function(table,key) return Window.prototype[key] end
13 
14 --下面是測試程式碼:15 w = Window.new{x = 10, y = 20}
16 print(w.width)   --輸出10017 print(w.width1)  --由於Window.prototype變數中也不存在該欄位,因此返回nil。
複製程式碼

    最後,Lua為__index元方法提供了一種更為簡潔的表示方式,如:Window.mt.__index = Window.prototype。該方法等價於上例中的匿名函式表示方法。相比而言,這種簡潔的方法執行效率更高,但是函式的方法擴充套件性更強。
    如果想在訪問table時禁用__index元方法,可以通過函式rawget(table,key)完成。通過該方法並不會加速table的訪問效率。

    2). __newindex元方法:
    和__index不同的是,該元方法用於不存在鍵的賦值,而前者則用於訪問。當對一個table中不存在的索引賦值時,直譯器就會查詢__newindex元方法。如果有就呼叫它,而不是直接賦值。如果這個元方法指向一個table,Lua將對此table賦值,而不是對原有的table賦值。此外,和__index一樣,Lua也同樣提供了避開元方法而直接操作當前table的函式rawset(table,key,value),其功能類似於rawget(table,key)。

    3). 具有預設值的table:
    預設情況下,table的欄位預設值為nil。但是我們可以通過元表修改這個預設值,如:

複製程式碼
1 function setDefault(table,default)
2     local mt = {__index = function() return default end }
3     setmetatable(table,mt)
4 end
5 tab = {x = 10, y = 20}
6 print(tab.x,tab.z)  --10    nil7 setDefault(tab,0)
8 print(tab.x,tab.z)  --10    0
複製程式碼


    4). 跟蹤table的訪問:
    __index和__newindex都是在table中沒有所需訪問的index時才發揮作用的。因此,為了監控某個table的訪問狀況,我們可以為其提供一個空table作為代理,之後再將__index和__newindex元方法重定向到原來的table上,見如下程式碼:

複製程式碼
 1 t = {}        --原來的table 2 local _t = t  --保持對原有table的私有訪問。 3 t = {}        --建立代理 4 --建立元表 5 local mt = {
 6     __index = function(table,key)
 7         print("access to element " .. tostring(key))
 8         return _t[key]  --通過訪問原來的表返回欄位值 9     end,
10     
11     __newindex = function(table,key,value)
12         print("update of element " .. tostring(key) .. " to " .. tostring(value))
13         _t[key] = value  --更新原來的table14     end
15 }
16 setmetatable(t,mt)
17 
18 t[2] = "hello"
19 print(t[2])
20 
21 --輸出結果為22 --update of element 2 to hello23 --access to element 224 --hello
複製程式碼


    5). 只讀的table:
    通過代理的概念,可以很容易的實現只讀table。只需跟蹤所有對table的更新操作,並引發一個錯誤即可,見如下示例程式碼:

複製程式碼
 1 function readOnly(t)
 2     local proxy = {}
 3     local mt = {
 4         __index = t,
 5         __newindex = function(t,k,v)
 6             error("attempt to update a read-only table")
 7         end
 8     }
 9     setmetatable(proxy,mt)
10     return proxy
11 end
12 
13 days = readOnly{"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
14 print(days[1])
15 days[2] = "Noday"
16 
17 --輸出結果為:18 --[[19 Sunday
20 lua: d:/test.lua:6: attempt to update a read-only table
21 stack traceback:
22         [C]: in function 'error'
23         d:/test.lua:6: in function <d:/test.lua:5>
24         d:/test.lua:15: in main chunk
25         [C]: ?
26 ]]--

相關推薦

Step By Step(Lua方法)

 Lua中提供的元表是用於幫助Lua資料變數完成某些非預定義功能的個性化行為,如兩個table的相加。假設a和b都是table,通過元表可以定義如何計算表示式a+b。當Lua試圖將兩個table相加時,它會先檢查兩者之一是否有元表,然後檢查該元表中是否存在__add欄位,

Lua中的方法

類型 得到 算術 自己的 連接 還記得 clas 是否 操作符 前言Lua中每一個值都可具有元表。 元表是普通的Lua表,定義了原始值在某些特定操作下的行為。你可通過在值的原表中設置特定的字段來改變作用於該值的操作的某些行為特征。比如。當數字值作為加法的操作數時,Lua檢

lua方法

方法 int 找到 註意 dex index cnblogs 沒有 val setmetatable()函數設置元表,getmetatable()函數獲取元表 給一個table添加元表: t = {} t_metatable = {__index = {key = "va

Lua方法詳解(轉)

Lua中提供的元表是用於幫助Lua資料變數完成某些非預定義功能的個性化行為,如兩個table的相加。假設a和b都是table,通過元表可以定義如何計算表示式a+b。當Lua試圖將兩個table相加時,它會先檢查兩者之一是否有元表,然後檢查該元表中是否存在_

Lua方法介紹

一、基本介紹:         1.Lua中的每個值都可以有一個metatable,這個 metatable 就是一個原始的 Lua table  (metatable 中的鍵名為 事件 (event) ,把其中的值叫作 元方法 (metamethod))        

Lua中的方法學習總結(__index, __newindex)

前言 元表對應的英文是metatable,元方法是metamethod。我們都知道,在C++中,兩個類是無法直接相加的,但是,如果你過載了“+”符號,就可以進行類的加法運算。在Lua中也有這個道理,兩個table型別的變數,你是無法直接進行“+”操作的,如果你定義了一個

Step By Step(Lua編譯執行錯誤)

    1. 編譯:     Lua中提供了dofile函式,它是一種內建的操作,用於執行Lua程式碼塊。但實際上dofile只是一個輔助函式,loadfile才是真正的核心函式。相比於dofile,loadfile只是從指定的檔案中載入Lua程式碼塊,然後編譯這段程式碼

Step By Step(Lua模組包)

    從Lua 5.1開始,我們可以使用require和module函式來獲取和建立Lua中的模組。從使用者的角度來看,一個模組就是一個程式庫,可以通過require來載入,之後便得到一個型別為table的全域性變數。此時的table就像名字空間一樣,可以訪問其中的函式

【轉載】MDX Step by Step 讀書筆記(三) - Understanding Tuples (理解組)

將不 對比 處理 ren tuples 定位 用戶 結構 init 1. 在 Analysis Service 分析服務中,Cube (多維數據集) 是以一個多維數據空間來呈現的。在Cube 中,每一個緯度的屬性層次結構都形成了一個軸。沿著這個軸,在屬性層次結構上的每一個成

Step By Step(Lua目錄)

處理 叠代 類型 引用 持久化 系統 for 聲明 錯誤處理 Step By Step(Lua開篇)http://www.cnblogs.com/stephen-liu74/archive/2012/03/17/2403210.html一、簡介二、主要優勢三、應用場景Ste

VmWare 宿主主機通信 STEP BY STEP (適用於剛開始學習的人)

aid 並且 cap 應該 行程 最大的 mtu win7 bringing 基本原理 在虛擬機中有三種通信方式,例如以下圖所看到的 1. Bridged(橋接模式) 在橋接模式下,VMware虛擬出來的操作系統就像是局域網中的一獨立的主機

Step By Step(Lua調用C函數)

extern lua環境 class 數量 lsp 相同 虛擬 c語言代碼 cti 原文: http://www.cnblogs.com/stephen-liu74/archive/2012/07/23/2469902.html Lua可以調用C函數的能力將極大的提高Lu

lua——方法、繼承

我們 查詢 layer ati 一個表 offer 使用 consul == 【元表】 元表中的鍵為事件(event),稱值為元方法(metamethod)。 通過函數getmetatable查詢不論什麽值的元表,通過函數setm

lua以及方法

  轉載: https://www.cnblogs.com/Dong-Forward/p/6063365.html 知微出凡 lua元表以及元方法 lua中的變數是沒有資料型別的,值有型別。型別有八種nil,number,boolean, string, functio

Lua查詢表元素過程(、__index方法是如何工作的)

Lua表類似HashMap Lua的表本質其實是個類似HashMap的東西,其元素是很多的Key-Value對,如果嘗試訪問了一個表中並不存在的元素時,就會觸發Lua的一套查詢機制,也是憑藉這個機制來模擬了類似“繼承”的行為 舉例說明: local tempTable = {} tempTable.membe

Step By Step(Lua呼叫C函式)

Lua可以呼叫C函式的能力將極大的提高Lua的可擴充套件性和可用性。對於有些和作業系統相關的功能,或者是對效率要求較高的模組,我們完全可以通過C函式來實現,之後再通過Lua呼叫指定的C函式。對於那些可被Lua呼叫的C函式而言,其介面必須遵循Lua要求的形式,即typedef

Lua 中的方法

Lua中每個值都可具有元表。 元表是普通的Lua表,定義了原始值在某些特定操作下的行為。你可通過在值的原表中設定特定的欄位來改變作用於該值的操作的某些行為特徵。例如,當數字值 作為加法的運算元時,Lua檢查其元表中的"__add"欄位是否有個函式。如果有,Lua呼叫它執行加

Lua筆記——

當有一個表為a我們讓b = a,則b指向和a相同的表的記憶體我們修改b[1],則a[1]也變了我們令a=nil,則僅僅是說a指向空但那個表還在,故b仍然可用。當b =nil時,  則已經找不到這個表了但是其記憶體依然佔用著(因為已經無變數指向,故該記憶體屬於洩露記憶體可以用c

Tensorflow基礎知識神經網路構建--step by step 入門TensorFlow(一)

Tensorflow基礎知識與神經網路構建–step by step 入門TensorFlow(一) 標籤: Tensorflow 我們將採用Jupyter notebook互動式程式設計的方式,通過一步步程式碼的講解,學習Tensorflow程式設計。

學習mybatis-3 step by step 篇一

odi png environ factor 數據 不能 val 集成開發環境 start 一、搭建簡單mybatis-3環境(詳細的中文文檔) 集成開發環境:IDEA 項目:maven + mybatis-3 1、創建maven結構項目 含簡單,如下圖: 下一步後,填寫