lua元表概念

来源:互联网 发布:mac flac转mp3 编辑:程序博客网 时间:2024/05/19 04:52


元表,顾名思义,它本身就是一个table,再具体一点说,就是用来存放元方法的table。
元表的作用就是修改一个值的行为(更确切的说,这是元方法的能力),需要注意的是,这种修改会覆盖掉原本该值可能存在的相应的预定义行为。

1. lua中的每个值都可以有一个元表,只是table和userdata可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。
   lua代码中只能设置table的元表,至于其他类型值的元表只能通过C代码设置。
   多个table可以共享一个通用的元表,但是每个table只能拥有一个元表。

2. 算术类和关系类元方法主要有以下几种:
        __add   - 加法
        __sub   - 减法
        __mul   - 乘法
        __div   - 除法
        __unm   - 相反数
        __mod   - 取模
        __pow   - 乘幂
        __concat    - 描述连接操作符".."行为
        __eq    - 等于
        __le    - 小于等于
        __lt    - 小于
   对于二元操作符,按照从左到右的顺序从操作对象中查找对应的元方法。

3. 库定义的元方法主要有:
        __tostring  - 用于重定义各种类型的值的print输出。
                      调用print输出某个值时会检查该值是否有一个__tostring的元方法,如果有,就用这个元方法来生成输出字符串
        __metatable - 用于保护集合的元表,使用户既不能看也不能修改集合的元表
                      设置了该字段后,调用getmetatable就会返回该字段的值,而调用setmetatable则会引发一个错误

4. __index元方法用于改变访问table中不存在的key时的行为。
   当访问一个table中不存在的key时,如果在该table的元表中定义了__index字段,那么lua就会用调用该元方法,规则如下:
            当__index元方法是一个函数时,lua会用table和不存在的key作为入参来调用该函数;
            当__index元方法是一个table时,lua就会用不存在的key访问这个table。

   如果不想在访问一个table时自动调用到它的__index元方法,就需要使用函数rawget。

   一个典型的__index元方法应用案例就是实现具有默认值的table:

        function setDefault (t,d)
            local mt = {__index = function (t,k) return d end}
            setmetatable(t,mt)
            return t
        end
        tb = {1,2}
        setDefault(tb,6)
        print(tb[1],tb[10])    --> 1,6
        --[[
        以上代码中,调用了setDefault后,任何对tb中不存在字段的访问都将调用它的__index元方法,
        而这个元方法总是返回d的值(这里就是6)。

        但是用这种方式实现具有默认值的table存在一个缺陷:需要为每个具有默认值的table都创建一个对应的元表.
        --]]
             
        local mt = {__index = function (t,k) return t.___ end}
        function setDefault (t,d)
            t.___ = d
            setmetatable(t,mt)
        end
        tb = {1,2}
        setDefault(tb,6)
        print(tb[1],tb[10])    --> 1,6
        --[[
        以上代码中,独立出来的元表mt可以用于所有table,因为默认值被存储在每个table中的固定字段"___",而不再跟元表关联.
       
        但是这种方法需要注意命名冲突问题.
        --]]
       
        local key = {}
        local mt = {__index = function (t,k) return t[key] end}
        function setDefault (t,d)
            t[key] = d
            setmetatable(t,mt)
        end
        tb = {1,2}
        setDefault(tb,6)
        print(tb[1],tb[10])    --> 1,6
        --[[
        以上代码中,创建了一个独立的table,并用它的地址作为key,从而确保这个key在所有table中的唯一性
        --]]

5. __newindex元方法用于改变设置table中不存在的key时的行为。
   当对一个table中不存在的key赋值时,如果在该table的元表中定义了__newindex字段,那么lua就会调用该元方法,规则基本类似__index
  
   如果不想在设置一个table时自动调用到它的__newindex元方法,就需要使用函数rawset。

6. __index和__newindex的应用案例: 监视对一个table的所有访问情况
   实现原理:为真正的table创建一个代理,这个代理就是一个空的table(只有将一个table永远保持为空,才能捕捉到所有对它的访问),
             并通过__index和__newindex将访问重定向到原来的table上。
               
        local tb = {1,2}    -- 构造一个需要被监控的table
        local _tb = tb      -- 保持对原table的一个私有访问
        tb = {}             -- 创建代理
       
        local mt = {
            __index =
            function (t,k)
                print("access to element " .. tostring(k))
                return _tb[k]
            end,
            __newindwx =
            function (t,k,v)
                print("update of element ".. tostring(k).. " to " .. tostring(v))
                _tb[k] = v
            end
        }

        setmetatable(tb,mt) -- 设置代理table的元表
       
        print(tb[1])    -- access to element 1
                        -- 1
        tb[3] = 3       -- update of element 3 to 3
        --[[
        以上代码中,原table被保存在一个私有变量_tb中,外界只能通过代理table来访问原table,而整个访问情况通过代理table的元表被记录下来.

        当然这种代理方法存在2个缺点:
            [1]. 无法遍历原来的table,因为pairs只能操作代理table
            [2]. 如果要同时监视多个table,就需要为每个table创建对应的元表,解决方法类似上面的"实现具有默认值的table"
        --]]

7. __index和__newindex的应用案例:实现只读的table
   实现原理:同样是为真正的table创建一个空的代理table,不同的是只需要监控所有对table的写操作,并引发一个错误就行。
             至于读操作,直接使用原table作为__index元方法即可。

        local tb = {1,2}    -- 构造一个需要被监控的table
        local _tb = tb      -- 保持对原table的一个私有访问
        tb = {}             -- 创建代理
       
        local mt = {
            __index = _tb,
            __newindex =
            function (t,k,v)
                error("attempt to update a read-only table",2)
            end
        }

        setmetatable(tb,mt) -- 设置代理table的元表
       
        print(tb[1])    -- 1
        tb[1] = 2       -- error: attempt to update a read-only table