zoukankan      html  css  js  c++  java
  • 《Programming in Lua 3》读书笔记(十)

    这一部分应该挺重要的,Lua中唯一的数据结构便是table,几乎所有的的数据操作都是在table的基础上进行。而本文提到的元表和元方法,便是帮助table实现更强大的功能而设计的。

    日期:2014.7.11

    Part Ⅱ 

    Metatables and Metamethods

    Lua中不能直接对table进行相加、比较等操作。除非使用元表(Metatables)。元表可以使得我们改变元素在处理未定义操作的应对行为,如定义两个table直接的相加操作。Lua在处理两个table的相加操作时会首先检查两个table是否有元表,且元表中是否有 __add 元方法字段,如果有这个字段则lua会遵循这个字段内定义的操作执行两个table的相加操作。

    Lua中各个类似的变量有一个相关联的元表?(到底有没有?),而table与userdata有各自独立的元表。默认的,新创建的table是没有元表的:
    e.g.
    t = {}
    print(getmetatable(t))          --nil
    此时我们可以通过setmetatable方法来设置元表,元表其实也相当于一个table
    e.g.
    t1 = {}
    setmetatable(t,t1)
    print(getmetatable(t) == t1)          --ture

    当然,在lua中我们只可以对table执行setmetatable操作,对其余类型的变量执行这个操作需要使用C 代码。书上string库涉及到了给string类型变量执行设置元表的操作。其余类型的变量默认是没有元表的?
    print(getmetatable("ss"))               -- table
    print(getmetatable(10))                  --nil


    Arithmetic Metamethods
    算术运算元方法

    这里介绍元表的使用,在这里用一个table表示set,我们需要运算set的并集等操作
    e.g.
    Set = {} 
    local mt = {}          --metatable for sets
    
    function Set.new(l)     --新建一个set,初始化并且设置其元表
         local set = {}     
         setmetatable(set,mt)
         for _ v in ipairs(l) do set[v] = true end
         return set
    end

    这样每次我们新建一个set其都会有同样的元表:
    s1 = Set.new{10,20,11,13}
    s2 = Set.new{30,1}
    print(getmetatable(s1))          --table: 0x7fa1eb4093a0
    print(getmetatable(s2))          --table: 0x7fa1eb4093a0


    给元表添加元方法:__add 字段表示table如果执行相加操作
    mt.__add = Set.union     --注意此时的 __add 字段还是不能使用的,因为Set.union 还未定义,而且这段代码也需要放在定义Set.union之后,否则会报错。正确的用法是先定义再赋值
    --假若mt.__add = Set.union
    --定义Set.union
    function Set.union( a,b )
         local res = Set.new{}
         for k in pairs(a) do
              res[k] = true
         end
         for k in pairs(b) do
              res[k] = true
         end
    
         return res
    end
    --此时
    s3 = s1 + s2 会报错     --attempt to perform arithmetic on global 's1' (a table value)
    --需要将mt.__add = Set.union 放置在定义Set.union之后。

    同样的,设置 __mul 元方法也是类似的要求
    --定义 Set.intersection
    function Set.intersection( a,b )
         local res = Set.new{}
         for k in pairs(a) do
              res[k] = b[k]
         end
         return res
    end
    mt.__mul = Set.intersection

    所有的算术运算元方法:
    __add(加)、__mul(乘)、__sub(减)、__div(除)、__unm(负)、__mod(取模)、__pow(取幂)、__concat(连接)

    Lua在处理两个变量的算术运算时,针对不同类型的变量,如
    e.g.
    s = Set.new {1,2,3}
    s = s + 8
    此时运行的话会报错:
    --bad argument #1 to 'pairs' (table expected, got number) 
    其处理两个变量的算术运算遵循的步骤是:如果第一个变量定义了元方法则使用第一个变量的元方法,而不会再考虑第二个元素的元方法;第一个没有而第二个有,则使用第二个的;否则就会报错。
    因此为了更好的控制程序运行,我们需要限制两个变量为同类型拥有同一个元表,以__add为例,可以如下操作:
    function Set.union( a,b )
         if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
              error("xxx",2)     --注意这里的参数是2,表示是提醒用户是传递的参数有问题
         local res = Set.new{}
         for k in pairs(a) do
              res[k] = true
         end
         for k in pairs(b) do
              res[k] = true
         end
    
         return res
    end


    Relational Metamethods
    关系运算元方法
    Lua中的关系运算元方法主要有:
    __eq(相等)、__lt(小于)、__le(小于或等于)。而对于其他的关系操作符,Lua直接做了转换:a ~= b 相当于 not (a == b) 、a > b 相当于 b < a、a >= b 相当于 b <= a;
    关系运算元方法的具体使用类似于上文提到的算术运算元方法;
    要注意的是,当两个变量的元方法不同的时候执行相等关系运算会返回false;


    Library-Defined Metamethods
    库定义的元方法

    __tostring 元方法 
    当调用tostring函数的时候,函数首先会寻找变量是否有__tostring 元方法:
    同上文:
    s = Set.new{1,2,2}
    print(s)                                --table: 0x7f8349403d30
    print(getmetatable(s))           --table: 0x7f8349409fd0
    此时打印出来的并不是其值,但也不是其元表

    --to string
    function Set.tostring( set )
         local l = {}
         for e in pairs(set) do
              l[#l + 1] = e
         end
         return "{" .. table.concat(l," , ") .. "}"
    end

    mt.__tostring = Set.tostring
    print(s)                                   --{1,2,2}
    在设置了其元方法之后,才会正确的打印出其值

    当然我们可以通过一定的方法来保护我们的元表,setmetatabe和getmetatable也是使用到了元方法,我们可以根据这一特性达到我们的目的:
    e.g.
    mt.__metatable = "cannot change"
    s1 = Set.new{}
    print(getmetatable(s1))          --cannot change
    而当我们想改变其元表的时候
    e.g.
    setmetatable(s1,mt)               --error:cannot change a protected metatable 
    会报错,不能修改其元表,这样就达到了我们要保护元表的目的


    Table-Access Metamethods
    Lua允许通过元表来控制修改和访问table中不存在的元素的行为

    The __index metamehod
    当我们试图访问一个table中不存在的元素时,我们得到的值将会是nil。这是一般意义上将的,事实上,我们访问table中不存在的元素的时候,会触发编译器寻找__index 元方法,当没有该方法的时候会返回nil;而当该元方法存在被定义了,将会返回该方法定义的操作。
    这一个特性对我们使用继承机制,继承默认变量的时候有很大的帮助,书上也是以这个为例子做了讲解:
    --有默认变量的table
    prototype = {x = 0,y = 0,width = 100,height = 100}
    --元表
    mt = {}
    --构造函数
    function new(o)
         setmetatable(o,mt)
         return o
    end
    --定义元方法
    mt.__index = function(_,key)
         return prototype[key]
    end
    --创建一个新的table,使用继承机制,需要技能默认变量的table
    w = new{x = 10,y = 20}
    print(w.width)           --100
    此时w使用到了prototype里面的值

    __index 元方法不一定需要是一个函数,也可以是一个table。当该方法是一个函数的是,Lua会执行函数里定义的操作,当是一个table的时候,Lua直接在table中执行访问操作。

    函数 rawget(t,i) 可以使得我们访问table各个元素的时候不去调用 __index 操作.将会对t执行raw访问?啥意思

    The __newindex metamethod
    该元方法的作用表现在更新table中元素值的时候。当我们试图给table中不存在的键赋值的时候,编译器会寻找 __newindex 的元方法,如果有编译器将会执行该方法定义的操作,否则就会直接赋值。这里也有一个函数 rawset(t,k,v),该函数会绕开元方法,直接在t中对键k设置值v。
    书中提到,有效的结合__index 和 __newindex 两个元方法的使用将会带来很强大的设计技巧,如创建只读table,带默认值的table等。

    Tables with default values
    table带有默认的值,其原理主要是在我们访问一个table中不存在或者没有赋值的键的时候,返回值是一个固定值,这里就涉及到了 __index 元方法
    e.g.
    function setDefault( t,d )
         local mt = {__index = function ( ... )
              return d
         end}
         setmetatable(t,mt)
    end
    tab = {x = 10,y = 20}
    print(tab.x,tab.z)               --10,nil
    setDefault(tab,0)
    print(tab.x,tab.z)               --10,0
    以上操作就为tab设置了默认值0,如果试图访问tab中没有定义或者不存在的元素,返回值将会是0

    为多个不同的table执行设置多个不同默认值的操作
    e.g.
    local mt = {__index = function (t) return t.___ end}
    function setDefault (t,d)
         t.___ = d
         setmetatable(t,mt)
    end
    这里的技巧在于,元表的定义在函数的外部,且将默认值存储在了要设置默认值的table本身的内部。
    避免命名冲突的操作:
    e.g.
    local key = {}          --以一个table作为key
    local mt = {__index = function (t) return t[key] end}
    function setDefault (t,d)
         t[key] = d
         setmetatable(t,mt)
    end


    Tracking table accesses
    有效的使用__index 和 __newindex 可以帮助我们监控对table的访问和赋值操作。结合使用 proxy (代理) 便可以追踪所有对table的访问操作并且追踪到其访问的值。书上提到的只有当table为空的时候才能捕获到所有对其的访问操作,为啥?
    t = {}     --original table
    --keep a private access to the original table
    local _t = t
    --create proxy 代理
    t = {}
    --create metatable
    local mt = {
         __index = function ( t,k )
              print("*access to element " .. tostring(k))
              return _t[k]
         end,
    
         __newindex = function ( t,k,v )
              print("*update of element " .. tostring(k) .. " to " .. tostring(v))
              _t[k] = v      --update original table
         end
    }
    setmetatable(t,mt)
    
    t[2] = "hello"
    print(t[2])
    打印出来的,追踪了table从赋值到访问的过程
    *update of element 2 to hello
    *access to element 2
    hello


    Read-only tables
    只读table

    只读table的原理主要就是在试图给table赋值的时候做限制,这里就涉及到了__newindex 元方法的使用。
    e.g.
    --read only table
    function readOnly (t)
         local proxy = {}
         local mt = {
              __index = t,
              __newindex = function (t,k,v)
                   error("attempt to update a read-only table",2)      --在试图改变元素的时候抛出错误,且参数为2,表示在报错的地方将会是调用该方法的地方。
              end
         }
         setmetatable(proxy,mt)
         return proxy
    end
    通过在__newindex元方法里面做恰当的修改,便能将我们的table改写为只读table。


  • 相关阅读:
    【Nginx】ngx_event_core_module模块
    ELMAH--Using HTTP Modules and Handlers to Create Pluggable ASP.NET Components 77 out of 90 rated th
    nyist oj 214 单调递增子序列(二) (动态规划经典)
    java 入门书籍(java7)
    ARCGIS将WGS84坐标投影到高斯平面
    【linux】linux下对java程序生成dump文件,并使用IBM Heap Analyzer进行分析,查找定位内存泄漏的问题代码
    【springboot】【socket】spring boot整合socket,实现服务器端两种消息推送
    【linux】linux修改open file 大小
    【docker】docker限制日志文件大小的方法+查看日志文件的方法
    【docker】docker部署spring boot服务,但是docker logs查看容器输出控制台日志,没有日志打印,日志未打印,docker logs不打印容器日志
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/4044576.html
Copyright © 2011-2022 走看看