openresty开发系列22--lua的元表

openresty开发系列22--lua的元表


举个例子,在 Lua table 中咱们能够访问对应的key来获得value值,可是却没法对两个 table 进行操做。

那如何计算两个table的相加操做a+b?

local t1 = {1,2,3}
local t2 = {4,5,6}

local t3 = t1 + t2   ---->  {1,2,3,4,5,6}

相似java的一些操做重载

这种相似的需求,lua 提供了元表(Metatable)和元方法,容许咱们改变table的行为,每一个行为关联了对应的元方法。

1)setmetatable(table,metatable): 对指定table设置元表(metatable),
若是元表(metatable)中存在__metatable键值,setmetatable会失败 。

2)getmetatable(table): 返回对象的元表(metatable)。

mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表

等价于:
mytable = setmetatable({},{})

返回对象元表:
getmetatable(mytable)                 -- 返回mymetatable


元方法的命名都是以 __ 两个下划线开头。

一)__index 元方法
对表读取索引一个元方法

这是 metatable 最经常使用的键。

当你经过键来访问 table 的时候,若是这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)
中的__index元方法。若是__index是一个表,Lua会在表格中查找相应的键。

local t = {}              -- 普通表t为空
local other = {foo = 2}   -- 元表 中有foo值
setmetatable(t,{__index = other})     -- 把 other 设为 t 的元表__index
print(t.foo);  ---输出 2
print(t.bar);  ---输出nil

t.foo为何会输出2,就是由于咱们重写了__index索引的重载,lua在执行中若是t中没有foo,
就会在他的元表中__index中去找,__index等于other,就输出2。

----------------

若是咱们把t设置一下foo的值为3,在看看结果
local t = {foo = 3 }                  -- 普通表t为空
local other = {foo = 2}               -- 元表 中有foo值
setmetatable(t,{__index = other})     -- 把 other 设为 t 的元表__index
print(t.foo);  ---输出 3
print(t.bar);  ---输出nil

---------------------------------------------

若是__index赋值为一个函数的话,Lua就会调用那个函数,table和键会做为参数传递给函数。
__index 元方法查看表中元素是否存在,若是不存在,返回结果为 nil;若是存在则由 __index 返回结果。

local t = {key1 = "value1" }            
local function metatable(mytable,key)
   if key == "key2" then
      return "metatablevalue"
   else
      return nil
   end
end
setmetatable(t,{__index = metatable})     
print(t.key1);
print(t.key2);  
print(t.key3);  

分析:

print(t.key1);  ---这个输出value1 ,是由于t表中有此key
print(t.key2);  ---这个输出metatablevalue,是由于t表中没有此key,就会调用t的元表中的元方法__index,
                ---这是__index是个函数,就会执行这个函数,传t表和key值这两个参数到此函数,
                ---函数体中判断有此key2 就输出metatablevalue;

print(t.key3);  ---这个输出nil,是由于t表没有,元方法__index函数中 对key3返回nil值

--------------------------------------

总结:lua对表索引取值的步骤

Lua查找一个表元素时的规则,其实就是以下3个步骤:
1.在表中查找,若是找到,返回该元素,找不到则继续步骤2
2.判断该表是否有元表,若是没有元表,返回nil,有元表则继续步骤3。
3.判断元表有没有__index元方法,若是__index方法为nil,则返回nil;
  若是__index方法是一个表,则重复一、二、3;
  若是__index方法是一个函数,则执行该函数,获得返回值。


二)__newindex 元方法

__newindex 元方法用来对表更新,__index则用来对表访问 。

当你给表进行索引进行赋值,但此索引不存在;lua会查找是否有元表,有元表就会查找__newindex 元方法是否存在:
若是存在则调用__newindex的值进行执行操做,但不会对原表进行赋值操做。

如下实例演示了 __newindex 元方法的应用:

mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })

print("mytable.key1=",mytable.key1)
mytable.newkey = "新值2"

print("mytable.newkey=",mytable.newkey)
print("mymetatable.newkey=",mymetatable.newkey)

mytable.key1 = "新值1"
print("mytable.key1=",mytable.key1)
print("mymetatable.key1=",mymetatable.key1)

以上实例中表设置了元方法 __newindex
在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而对mytable原表不进行赋值。
而对mytable已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。

----------------------------

若是咱们要对原来的table进行赋值,那咱们就能够用rawset;;__newindex函数会传三个参数,
mytable = setmetatable({key1 = "value1"}, {
    __newindex = function(t,k,v)    ---第一个参数为table,第二个参数为key,第三个参数为value
        rawset(t,k,v);
    end
})
mytable.key1 = "new value"
mytable.key2 = 4
print("mytable.key1=",mytable.key1)
print("mytable.key2=",mytable.key2)

key2本来是再也不mytable表中的,经过元方法__newindex中函数使用了rawset,就能够对原table进行赋值。

三)为表添加操做符“+”

咱们这里定义“+”这元方法,把它定义为两个table相连

t1={1,2,3}  
t2={4,5,6}
t1 + t2 相加的结果,咱们想获得的是 {1,2,3,4,5,6}
那咱们如何写元表?
“+”对应的元方法为__add

local function add(mytable,newtable)
    local num = table.maxn(newtable)
    for i = 1, num do
      table.insert(mytable,newtable[i])
  end
  return mytable
end

local t1 = {1,2,3}
local t2 = {4,5,6}

setmetatable(t1,{__add = add})

t1 = t1 + t2

for k,v in ipairs(t1) do
    print("key=",k," value=",v)
end

这样咱们就实现了两个table相加


如下是咱们的操做符对应关系
模式                    描述
__add                 对应的运算符 '+'.
__sub                 对应的运算符 '-'.
__mul                 对应的运算符 '*'.
__div                 对应的运算符 '/'.
__mod                 对应的运算符 '%'.
__unm                 对应的运算符 '-'.
__concat              对应的运算符 '..'.
__eq                  对应的运算符 '=='.
__lt                  对应的运算符 '<'.
__le                  对应的运算符 '<='.


四)__call元方法
__call元方法在 Lua 调用一个值时调用。如下实例演示了计算两个表中全部值相加的和:

相似的 t();相似函数调用

local function call(mytable,newtable)
    local sum = 0
    local i
    for i = 1, table.maxn(mytable) do
        sum = sum + mytable[i]
    end
    for i = 1, table.maxn(newtable) do
        sum = sum + newtable[i]
    end
    return sum
end
local t1 = {1,2,3}
local t2 = {4,5,6}
setmetatable(t1,{__call = call})

local sum = t1(t2)     
print(sum)


五)__tostring 元方法
__tostring 元方法用于修改表的输出行为。如下实例咱们自定义了表的输出内容,把表中全部的元素相加输出:
local t1 = {1,2,3}

setmetatable(t1,{
    __tostring = function(mytable)
        local sum = 0
        for k, v in pairs(mytable) do
            sum = sum + v
        end
        return "all value sum =" .. sum
    
    end
})
print(t1)    ----print方法会调用table的tostring元方法   

到此咱们的元表 和 元方法 就讲完了,这个是须要你们本身动手去测试体验的。要有领悟能力



六)点号与冒号操做符的区别

local str = "abcde"
print("case 1:", str:sub(1, 2))
print("case 2:", str.sub(str, 1, 2))

执行结果
case 1: ab
case 2: ab

冒号操做会带入一个 self 参数,用来表明 本身。而点号操做,只是 内容 的展开。
在函数定义时,使用冒号将默认接收一个 self 参数,而使用点号则须要显式传入 self 参数。
obj = { x = 20 }
function obj:fun1()
    print(self.x)
end
等价于
obj = { x = 20 }
function obj.fun1(self)
    print(self.x)
end
注意:冒号的操做,只有当变量是类对象时才须要。

java

相关文章
相关标签/搜索