• 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方法是一个表,则重复1、2、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
    注意:冒号的操作,只有当变量是类对象时才需要。

  • 相关阅读:
    Ubuntu下手动安装vscode
    VMware Tools安装后设置自动挂载解决共享文件夹无法显示的问题
    VMware Tools安装方法及共享文件夹设置方法
    JavaScript原始类型转换和进制转换
    Javascript的数据类型(原始类型和引用类型)
    设计模式(六)观察者模式
    设计模式(五)之适配器模式
    设计模式(四)注册模式 解决:解决全局共享和交换对象
    设计模式(三)单例模式
    设计模式(二)之策略模式
  • 原文地址:https://www.cnblogs.com/reblue520/p/11433952.html
Copyright © 2020-2023  润新知