多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

中國最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2

lua教程

Lua中的元表與元方法

閱讀 (2390)

前言

Lua中每個值都可具有元表。 元表是普通的Lua表,定義了原始值在某些特定操作下的行為。你可通過在值的原表中設置特定的字段來改變作用于該值的操作的某些行為特征。

例如,當數字值作為加法的操作數時,Lua檢查其元表中的"__add"字段是否有個函數。如果有,Lua調用它執行加法。

我們稱元表中的鍵為事件(event),稱值為元方法(metamethod)。前述例子中的事件是"add",元方法是執行加法的函數。

可通過函數getmetatable查詢任何值的元表。

在table中,我可以重新定義的元方法有以下幾個:

__add(a, b) --加法
__sub(a, b) --減法
__mul(a, b) --乘法
__div(a, b) --除法
__mod(a, b) --取模
__pow(a, b) --乘冪
__unm(a) --相反數
__concat(a, b) --連接
__len(a) --長度
__eq(a, b) --相等
__lt(a, b) --小于
__le(a, b) --小于等于
__index(a, b) --索引查詢
__newindex(a, b, c) --索引更新(PS:不懂的話,后面會有講)
__call(a, ...) --執行方法調用
__tostring(a) --字符串輸出
__metatable --保護元表

Lua中的每一個表都有其Metatable。Lua默認創建一個不帶metatable的新表

t = {}
print(getmetatable(t)) --> nil

可以使用setmetatable函數設置或者改變一個表的metatable

t1 = {}
setmetatable(t, t1)
assert(getmetatable(t) == t1)

任何一個表都可以是其他一個表的metatable,一組相關的表可以共享一個metatable(描述他們共同的行為)。一個表也可以是自身的metatable(描述其私有行為)。
接下來就介紹介紹如果去重新定義這些方法。

算術類的元方法

現在我使用完整的實例代碼來詳細的說明算術類元方法的使用。

Set = {}
local mt = {} -- 集合的元表

-- 根據參數列表中的值創建一個新的集合
function Set.new(l)
    local set = {}
     setmetatable(set, mt)
    for _, v in pairs(l) do set[v] = true end
     return set
end

-- 并集操作
function Set.union(a, b)
    local retSet = Set.new{} -- 此處相當于Set.new({})
    for v in pairs(a) do retSet[v] = true end
    for v in pairs(b) do retSet[v] = true end
    return retSet
end

-- 交集操作
function Set.intersection(a, b)
    local retSet = Set.new{}
    for v in pairs(a) do retSet[v] = b[v] end
    return retSet
end

-- 打印集合的操作
function Set.toString(set)
     local tb = {}
     for e in pairs(set) do
          tb[#tb + 1] = e
     end
     return "{" .. table.concat(tb, ", ") .. "}"
end

function Set.print(s)
     print(Set.toString(s))
end

現在,我定義“+”來計算兩個集合的并集,那么就需要讓所有用于表示集合的table共享一個元表,并且在該元表中定義如何執行一個加法操作。首先創建一個常規的table,準備用作集合的元表,然后修改Set.new函數,在每次創建集合的時候,都為新的集合設置一個元表。代碼如下:

Set = {}
local mt = {} -- 集合的元表

-- 根據參數列表中的值創建一個新的集合
function Set.new(l)
    local set = {}
     setmetatable(set, mt)
    for _, v in pairs(l) do set[v] = true end
     return set
end

在此之后,所有由Set.new創建的集合都具有一個相同的元表,例如:

local set1 = Set.new({10, 20, 30})
local set2 = Set.new({1, 2})
print(getmetatable(set1))
print(getmetatable(set2))
assert(getmetatable(set1) == getmetatable(set2))

最后,我們需要把元方法加入元表中,代碼如下:

mt.__add = Set.union

這以后,只要我們使用“+”符號求兩個集合的并集,它就會自動的調用Set.union函數,并將兩個操作數作為參數傳入。比如以下代碼:

local set1 = Set.new({10, 20, 30})
local set2 = Set.new({1, 2})
local set3 = set1 + set2
Set.print(set3)

在上面列舉的那些可以重定義的元方法都可以使用上面的方法進行重定義。現在就出現了一個新的問題,set1和set2都有元表,那我們要用誰的元表啊?雖然我們這里的示例代碼使用的都是一個元表,但是實際coding中,會遇到我這里說的問題,對于這種問題,Lua是按照以下步驟進行解決的:

  1. 對于二元操作符,如果第一個操作數有元表,并且元表中有所需要的字段定義,比如我們這里的__add元方法定義,那么Lua就以這個字段為元方法,而與第二個值無關;
  2. 對于二元操作符,如果第一個操作數有元表,但是元表中沒有所需要的字段定義,比如我們這里的__add元方法定義,那么Lua就去查找第二個操作數的元表;
  3. 如果兩個操作數都沒有元表,或者都沒有對應的元方法定義,Lua就引發一個錯誤。

以上就是Lua處理這個問題的規則,那么我們在實際編程中該如何做呢?
比如set3 = set1 + 8這樣的代碼,就會打印出以下的錯誤提示:

lua: test.lua:16: bad argument #1 to 'pairs' (table expected, got number)

但是,我們在實際編碼中,可以按照以下方法,彈出我們定義的錯誤消息,代碼如下:

function Set.union(a, b)
     if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
          error("metatable error.")
     end

    local retSet = Set.new{} -- 此處相當于Set.new({})
    for v in pairs(a) do retSet[v] = true end
    for v in pairs(b) do retSet[v] = true end
    return retSet
end

當兩個操作數的元表不是同一個元表時,就表示二者進行并集操作時就會出現問題,那么我們就可以打印出我們需要的錯誤消息。

上面總結了算術類的元方法的定義,關系類的元方法和算術類的元方法的定義是類似的,這里不做累述。

__tostring元方法

寫過Java或者C#的人都知道,Object類中都有一個tostring的方法,程序員可以重寫該方法,以實現自己的需求。在Lua中,也是這樣的,當我們直接print(a)(a是一個table)時,是不可以的。那怎么辦,這個時候,我們就需要自己重新定義tostring元方法,讓print可以格式化打印出table類型的數據。
函數print總是調用tostring來進行格式化輸出,當格式化任意值時,tostring會檢查該值是否有一個
tostring的元方法,如果有這個元方法,tostring就用該值作為參數來調用這個元方法,剩下實際的格式化操作就由__tostring元方法引用的函數去完成,該函數最終返回一個格式化完成的字符串。例如以下代碼:

mt.__tostring = Set.toString

如何保護我們的“奶酪”——元表

我們會發現,使用getmetatable就可以很輕易的得到元表,使用setmetatable就可以很容易的修改元表,那這樣做的風險是不是太大了,那么如何保護我們的元表不被篡改呢?在Lua中,函數setmetatable和getmetatable函數會用到元表中的一個字段,用于保護元表,該字段是metatable。當我們想要保護集合的元表,是用戶既不能看也不能修改集合的元表,那么就需要使用metatable字段了;當設置了該字段時,getmetatable就會返回這個字段的值,而setmetatable則會引發一個錯誤;如以下演示代碼:

function Set.new(l)
    local set = {}
     setmetatable(set, mt)
    for _, v in pairs(l) do set[v] = true end
     mt.__metatable = "You cannot get the metatable" -- 設置完我的元表以后,不讓其他人再設置
     return set
end

local tb = Set.new({1, 2})
print(tb)

print(getmetatable(tb))
setmetatable(tb, {})

上述代碼就會打印以下內容:

{1, 2}
You cannot get the metatable
lua: test.lua:56: cannot change a protected metatable

__index元方法

是否還記得當我們訪問一個table中不存在的字段時,會返回什么值?默認情況下,當我們訪問一個table中不存在的字段時,得到的結果是nil。但是這種狀況很容易被改變;Lua是按照以下的步驟決定是返回nil還是其它值得:

  1. 當訪問一個table的字段時,如果table有這個字段,則直接返回對應的值;
  2. 當table沒有這個字段,則會促使解釋器去查找一個叫__index的元方法,接下來就就會調用對應的元方法,返回元方法返回的值;
  3. 如果沒有這個元方法,那么就返回nil結果。

下面通過一個實際的例子來說明__index的使用。假設要創建一些描述窗口,每個table中都必須描述一些窗口參數,例如顏色,位置和大小等,這些參數都是有默認值得,因此,我們在創建窗口對象時可以指定那些不同于默認值得參數。

Windows = {} -- 創建一個命名空間

-- 創建默認值表
Windows.default = {x = 0, y = 0, width = 100, height = 100, color = {r = 255, g = 255, b = 255}}

Windows.mt = {} -- 創建元表

-- 聲明構造函數
function Windows.new(o)
     setmetatable(o, Windows.mt)
     return o
end

-- 定義__index元方法
Windows.mt.__index = function (table, key)
     return Windows.default[key]
end

local win = Windows.new({x = 10, y = 10})
print(win.x)               -- >10 訪問自身已經擁有的值
print(win.width)          -- >100 訪問default表中的值
print(win.color.r)          -- >255 訪問default表中的值

根據上面代碼的輸出,結合上面說的那三步,我們再來看看,print(win.x)時,由于win變量本身就擁有x字段,所以就直接打印了其自身擁有的字段的值;print(win.width),由于win變量本身沒有width字段,那么就去查找是否擁有元表,元表中是否有index對應的元方法,由于存在index元方法,返回了default表中的width字段的值,print(win.color.r)也是同樣的道理。

在實際編程中,__index元方法不必一定是一個函數,它還可以是一個table。當它是一個函數時,Lua以table和不存在key作為參數來調用該函數,這就和上面的代碼一樣;當它是一個table時,Lua就以相同的方式來重新訪問這個table,所以上面的代碼也可以是這樣的:

-- 定義__index元方法
Windows.mt.__index = Windows.default

__newindex元方法

newindex元方法與index類似,newindex用于更新table中的數據,而index用于查詢table中的數據。當對一個table中不存在的索引賦值時,在Lua中是按照以下步驟進行的:

Lua解釋器先判斷這個table是否有元表;

  1. 如果有了元表,就查找元表中是否有__newindex元方法;如果沒有元表,就直接添加這個索引,然后對應的賦值;
  2. 如果有這個__newindex元方法,Lua解釋器就執行它,而不是執行賦值;
  3. 如果這個__newindex對應的不是一個函數,而是一個table時,Lua解釋器就在這個table中執行賦值,而不是對原來的table。

那么這里就出現了一個問題,看以下代碼:

local tb1 = {}
local tb2 = {}

tb1.__newindex = tb2
tb2.__newindex = tb1

setmetatable(tb1, tb2)
setmetatable(tb2, tb1)

tb1.x = 10

發現什么問題了么?是不是循環了,在Lua解釋器中,對這個問題,就會彈出錯誤消息,錯誤消息如下:

loop in settable

引用博客:http://www.jellythink.com/archives/511

關閉
程序員人生
主站蜘蛛池模板: 精品国产毛片 | 中文字幕第315页 | 欧美亚洲另类图片 | 宅男午夜 | 精品亚洲77777www | 久久久久嫩草影院精品 | 欧美激情一区二区三区在线播放 | 国产精品一区二区国产 | japanesefree高清日本护士 | 欧美性久久久久 | 美女无遮挡免费视频网站 | 国产精品高清久久久久久久 | 日本在线免费 | 日韩欧美亚洲国产精品字幕久久久 | 武则天a级在线观看 | 国产一区二区三区久久 | 精品一区二区三区高清免费不卡 | 高清日本一级特黄aa大片 | 性freesexvideo xxx| 青青国产成人精品视频 | 欧美做爰孕妇群 | 男女羞羞网站 | 欧美性精品videofree | a级特黄毛片免费观看 | 日韩欧美中文字幕一区 | 国产精品video | 精品久久一区二区 | 亚洲黄色大全 | 国产亚洲精品精品国产亚洲综合 | 欧美极品videosvideo激情 欧美极品video粗暴 | 欧美影院一区二区三区 | 亚洲精品美女久久777777 | 男人边吃奶边玩下面舒服 | 亚洲一区二区三区91 | 亚洲视频中文字幕在线观看 | 黑人双渗透 | 欧美又大粗又爽又黄大片视频黑人 | 中文字幕天天躁夜夜狠狠综合 | 亚洲视频在线观看 | 亚洲爱爱图 | 国产精品第页 |