• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

lua元表和元方法《lua程序设计》13章读书笔记

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

lua中每个值都有一个元表,talble和userdata可以有各自独立的元表,而其它类型的值则共享其类型所属的单一元表。lua在创建table时不会创建元表。

t = {}
print(getmetatable(t))  --显示过元表 此时是nil

--可以用setmetatable来设置或修改任何table的元表
t1 = {}
setmetatable(t,t1)
assert(getmetatable(t) == t1)

任何table可以作为任何值的元表,而一组相关的table可以共享一个通用的元表,此元表描述了一个共同的行为。一个tabel甚至可以作为它自己的元表,用于描述其特有行为。

在lua中,只能设置table的元表。要设置其它类型的元表,必须通过C代码来完成

print(getmetatable("hi"))  --005DECD8 说明字符串有元表
print(getmetatable(10))  --number没有元表

13.1  算术类的元方法

Set = {}  --集合

local mt = {}  --集合元表

--根据参数列表中的值创建一个新的集合
function Set.new(l)
    local set = {}
    setmetatable(set,mt)  --指定 table set的元表为mt
    for k,v in ipairs(l) do
        set[v] = true    --注意,是拿索来当数据用的
    end
    return set
end
function Set.union(a,b)
    local res = Set.new{}
    for k,v in pairs(a) do res[k] = true end
    for k,v in pairs(b) do res[k] = true end
    return res
end

function Set.intersection(a,b)
    local res = Set.new{}
    for k,v in pairs(a) do
        if b[k] then
            res[k] = true
        end
    end
    return res
end

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

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


--将元方法加入元表
mt.__add = Set.union   --指定加号为求并集的方法
mt.__mul = Set.intersection  --指定乘号为交集的方法

s1 = Set.new{11,22,31,44,56}
s2 = Set.new{66,33,22,31}
s3 = s1 + s2 --求并集   
Set.print(s3) --输出 {11, 31, 66, 22, 33, 56, 44}
s4 = s1 * s2 --求交集
Set.print(s4) --输出 {31, 22}

13.2 关系类元方法

关系是指 __eq(等于)、__lt(小于)等

mt.__le = function(a,b)
     for k in pairs(a) do
        if not b[k] then  return false end
     end
     return true
end

mt.__lt = function(a,b)
    return a<=b and not (b<=a)
end

mt.__eq = function(a,b)
    return a<=b and b<=a
end

ss1 = Set.new{2,4}
ss2 = Set.new{4,10,2}
print(ss1<=ss2)  --true
print(ss1<ss2)   --true
print(ss1>=ss1)  --true
print(ss1>ss1)   --false
print(ss1 == ss2*ss1)  --true

13.3 库定义的元方法

tostring是一个典型的实例。它能将各种类型的值表示为简单的文本格式

print({}) ----table: 003ECEF0

函数总是调用tostring来格式化输出。当格式化任意值时,tostring会检测该值是否有一个 __tostring元方法。如果有,他就调用这个方法用来作为tostring的返回值

在集合实例中,我们定议了将任命表示为字符串的方法,我们可以设置元表的__tostring字段

mt.__tostring = Set.tostring
sstext = Set.new{33,55,6666}
print(sstext)  --{55, 33, 6666}

假设想要保护集合的元表,使用户即不能看也不能修改集合的元表。那么就需要用到__metatable。当设置了该字段时,getmetatable就会返回这个字段的值,而setmetatable会引发一个错误

mt.__metatable = "not your business"
sstext1 = Set.new{}  
print(getmetatable(sstext1))  --not your business
setmetatable(s1,{})

13.4 table 访问的元方法

13.4.1 __index元方法

当访问一个table中不存在的字段中时,如果这个字段不存在得到nil,但是如果这个table有一个元方法__index那么如果没有这个字段,就由这个元方法来提供结果

Window = {}

Window.prototype = {x=0,y=0,width = 100,height = 100}
Window.mt = {}

function Window.new(o)
    setmetatable(o,Window.mt)
    return o
end

--现在定义一个元方法
Window.mt.__index = function(table,key)
    return Window.prototype[key]
end


w = Window.new{x=10,y=20}
print(w.width)  -- 100 window实际上没有width这个字段

__index元方法还可以是一个table

13.4.2  __newindex元方法

与__index不同的是__index是在查询的时候用的而_newindes是在更新的时候用的

13.4.3具有默认值的table

以下代码为table设置默认值

function setDefault(t,d)
    local mt = {__index = function() return d end}
    setmetatable(t,mt)
end

13.4.4 跟踪table的访问

__index和__newindex都是在table中没有所需的index才发挥作用。因为只有table保持空才能捕捉到所有对他的访问,为了监视一个table的所有访问就得为真正的 table 创建一个代理

 

t_src = {}  --要跟踪的表
local _t = t_src

t = {} --创建代理

--创建元表
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
    end
}
setmetatable(t,mt)

t[2]  = "hello"  -- *update of element 2 to hello
print(t[2])  --*access to element 2

13.4.5 只读的table

只读table与上一节跟踪table类似,是通过__newindex来限制修改table内存

rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效。(我这里用到"重写"二字,可能不太对,希望能得到纠正)
    Window = {}  
      
    Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}  
    Window.mt = {}  
    function Window.new(o)  
        setmetatable(o ,Window.mt)  
        return o  
    end  
    Window.mt.__index = function (t ,key)  
        return 1000  
    end  
    Window.mt.__newindex = function (table ,key ,value)  
        if key == "wangbin" then  
            rawset(table ,"wangbin" ,"yes,i am")  
        end  
    end  
    w = Window.new{x = 10 ,y = 20}  
    print(rawget(w ,w.wangbin))  

打印结果是:nil。这里的元表中__index函数就不再起作用了。

 

但是rawset呢,起什么作用呢?我们再来运行一段代码。

    Window = {}  
    Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}  
    Window.mt = {}  
    function Window.new(o)  
        setmetatable(o ,Window.mt)  
        return o  
    end  
    Window.mt.__index = function (t ,key)  
        return 1000  
    end  
    Window.mt.__newindex = function (table ,key ,value)  
        table.key = "yes,i am"  
    end  
    w = Window.new{x = 10 ,y = 20}  
    w.wangbin = "55"  

然后我们的程序就stack overflow了。可见,程序陷入了死循环。因为w.wangbin这个元素本来就不存在表中,然后这里不断执行进入__newindex,陷入了死循环。

 

This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The third edition targets Lua 5.2 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


13.1 – Arithmetic Metamethods

In this section, we will introduce a simple example to explain how to use metatables. Suppose we are using tables to represent sets, with functions to compute the union of two sets, intersection, and the like. As we did with lists, we store these functions inside a table and we define a constructor to create new sets:

    Set = {}
    
    function Set.new (t)
      local set = {}
      for _, l in ipairs(t) do set[l] = true end
      return set
    end
    
    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
    
    function Set.intersection (a,b)
      local res = Set.new{}
      for k in pairs(a) do
        res[k] = b[k]
      end
      return res
    end
To help checking our examples, we also define a function to print sets:
    function Set.tostring (set)
      local s = "{"
      local sep = ""
      for e in pairs(set) do
        s = s .. sep .. e
        sep = ", "
      end
      return s .. "}"
    end
    
    function Set.print (s)
      print(Set.tostring(s))
    end

Now, we want to make the addition operator (`+´) compute the union of two sets. For that, we will arrange that all tables representing sets share a metatable and this metatable will define how they react to the addition operator. Our first step is to create a regular table that we will use as the metatable for sets. To avoid polluting our namespace, we will store it in the Set table:

    Set.mt = {}    -- metatable for sets
The next step is to modify the Set.new function, which creates sets. The new version has only one extra line, which sets mt as the metatable for the tables that it creates:
    function Set.new (t)   -- 2nd version
      local set = {}
      setmetatable(set, Set.mt)
      for _, l in ipairs(t) do set[l] = true end
      return set
    end
After that, every set we create with Set.new will have that same table as its metatable:
    s1 = Set.new{10, 20, 30, 50}
    s2 = Set.new{30, 1}
    print(getmetatable(s1))          --> table: 00672B60
    print(getmetatable(s2))          --> table: 00672B60

Finally, we add to the metatable the so-called metamethod, a field __add that describes how to perform the union:

    Set.mt.__add = Set.union
Whenever Lua tries to add two sets, it will call this function, with the two operands as arguments.

With the metamethod in place, we can use the addition operator to do set unions:

    s3 = s1 + s2
    Set.print(s3)  --> {1, 10, 20, 30, 50}
Similarly, we may use the multiplication operator to perform set intersection:
    Set.mt.__mul = Set.intersection
    
    Set.print((s1 + s2)*s1)     --> {10, 20, 30, 50}

For each arithmetic operator there is a corresponding field name in a metatable. Besides __add and __mul, there are __sub (for subtraction), __div (for division), __unm (for negation), and __pow (for exponentiation). We may define also the field __concat, to define a behavior for the concatenation operator.

When we add two sets, there is no question about what metatable to use. However, we may write an expression that mixes two values with different metatables, for instance like this:

    s = Set.new{1,2,3}
    s = s + 8
To choose a metamethod, Lua does the following: (1) If the first value has a metatable with an __add field, Lua uses this value as the metamethod, independently of the second value; (2) otherwise, if the second value has a metatable with an __add field, Lua uses this value as the metamethod; (3) otherwise, Lua raises an error. Therefore, the last example will call Set.union, as will the expressions 10 + s and "hy" + s.

Lua does not care about those mixed types, but our implementation does. If we run the s = s + 8 example, the error we get will be inside Set.union:

    bad argument #1 to `pairs' (table expected, got number)
If we want more lucid error messages, we must check the type of the operands explicitly before attempting to perform the operation:
    function Set.union (a,b)
      if getmetatable(a) ~= Set.mt or
         getmetatable(b) ~= Set.mt then
        error("attempt to `add' a set with a non-set value", 2)
      end
      ...  -- same as before

Metatable Events

wiki
 

A listing of all the 'special' keys in a metatable, and the metamethods which they perform.

 

  • __index - Control 'prototype' inheritance. When accessing "myTable[key]" and the key does not appear in the table, but the metatable has an __index property:
    • if the value is a function, the function is called, passing in the table and the key; the return value of that function is returned as the result.
    • if the value is another table, the value of the key in that table is asked for and returned
      • (and if it doesn't exist in that table, but that table's metatable has an __index property, then it continues on up)
    • Use "rawget(myTable,key)" to skip this metamethod.

 

  • __newindex - Control property assignment. When calling "myTable[key] = value", if the metatable has a __newindex key pointing to a function, call that function, passing it the table, key, and value.
    • Use "rawset(myTable,key,value)" to skip this metamethod.
    • (If the __newindex function does not set the key on the table (using rawset) then the key/value pair is not added to myTable.)

 

  • __mode - Control weak references. A string value with one or both of the characters 'k' and 'v' which specifies that the the keys and/or values in the table are weak references.
  • __call - Treat a table like a function. When a table is followed by parenthesis such as "myTable( 'foo' )" and the metatable has a __call key pointing to a function, that function is invoked (passing the table as the first argument, followed by any specified arguments) and the return value is returned.
  • __metatable - Hide the metatable. When "getmetatable( myTable )" is called, if the metatable for myTable has a __metatable key, the value of that key is returned instead of the actual metatable.

 

  • __tostring - Control string representation. When the builtin "tostring( myTable )" function is called, if the metatable for myTable has a __tostring property set to a function, that function is invoked (passing myTable to it) and the return value is used as the string representation.

 

  • __len - Control table length. When the table length is requested using the length operator ( '#' ), if the metatable for myTable has a __len key pointing to a function, that function is invoked (passing myTable to it) and the return value used as the value of "#myTable".

 

  • __gc - Userdata finalizer code. When userdata is set to be garbage collected, if the metatable has a __gc field pointing to a function, that function is first invoked, passing the userdata to it. The __gc metamethod is not called for tables. (See http://lua-users.org/lists/lua-l/2006-11/msg00508.html)

 

Mathematic Operators

 

  • __unm - Unary minus. When writing "-myTable", if the metatable has a __unm key pointing to a function, that function is invoked (passing the table), and the return value used as the value of "-myTable".
  • __add - Addition. When writing "myTable + object" or "object + myTable", if myTable's metatable has an __add key pointing to a function, that function is invoked (passing the left and right operators in order) and the return value used.
    • ''If both operands are tables, the left table is checked before the right table for the presence of an __add metaevent.
  • __sub - Subtraction. Similar to addition, using the '-' operator.
  • __mul - Multiplication. Similar to addition, using the '*' operator.
  • __div - Division. Similar to addition, using the '/' operator.
  • __mod - Modulo. Similar to addition, using the '%' operator.
  • __pow - Involution. Similar to addition, using the '^' operator.
  • __concat - Concatenation. Similar to addition, using the '..' operator.

 

Equivalence Comparison Operators

 

  • __eq - Check for equality. This method is invoked when "myTable1 == myTable2" is evaluated, but only if both tables have the exact same metamethod for __eq.
    • For example, see the following code:
    t1a = {}
    t1b = {}
    t2  = {}
    mt1 = { __eq = function( o1, o2 ) return 'whee' end }
    mt2 = { __eq = function( o1, o2 ) return 'whee' end }
    
    setmetatable( t1a, mt1 )
    setmetatable( t1b, mt1 )
    setmetatable( t2,  mt2 )
    
    print( t1a == t1b )     --> true
    print( t1a == t2 )      --> false
    
    • If the function returns nil or false, the result of the comparison is false; otherwise, the result is true.

 

      • If t1 and t2 are referencing the same table, the __eq method is not invoked for t1 == t2 :
function foo (o1, o2) 
  print( '__eq call' )
  return false 
end

t1 = {}
setmetatable( t1, {__eq = foo} )

t2 = t1
print( t1 == t2 ) --> true
        -- string '__eq call' not printed (and comparison result is true, not like the return value of foo(...)), so no foo(...) call here

t3 = {}
setmetatable( t3, {__eq = foo} )
if t1 == t3 then end  --> __eq call
        -- foo(...) was called

 

  • __lt - Check for less-than. Similar to equality, using the '<' operator.
    • Greater-than is evaluated by reversing the order of the operands passed to the __lt function.
    a > b == b < a
    

 

  • __le - Check for less-than-or-equal. Similar to equality, using the '<=' operator.
    • Greater-than-or-equal is evaluated by reversing the order of the operands passed to the __le function.
    a >= b == b <= a
    

RecentChanges · preferences
edit · history
Last edited December 19, 2014 5:15 am GMT (diff)

lua中每个值都有一个元表,talble和userdata可以有各自独立的元表,而其它类型的值则共享其类型所属的单一元表。lua在创建table时不会创建元表。

t = {}
print(getmetatable(t))  --显示过元表 此时是nil

--可以用setmetatable来设置或修改任何table的元表
t1 = {}
setmetatable(t,t1)
assert(getmetatable(t) == t1)

任何table可以作为任何值的元表,而一组相关的table可以共享一个通用的元表,此元表描述了一个共同的行为。一个tabel甚至可以作为它自己的元表,用于描述其特有行为。

在lua中,只能设置table的元表。要设置其它类型的元表,必须通过C代码来完成

print(getmetatable("hi"))  --005DECD8 说明字符串有元表
print(getmetatable(10))  --number没有元表

13.1  算术类的元方法

Set = {}  --集合

local mt = {}  --集合元表

--根据参数列表中的值创建一个新的集合
function Set.new(l)
    local set = {}
    setmetatable(set,mt)  --指定 table set的元表为mt
    for k,v in ipairs(l) do
        set[v] = true    --注意,是拿索来当数据用的
    end
    return set
end
function Set.union(a,b)
    local res = Set.new{}
    for k,v in pairs(a) do res[k] = true end
    for k,v in pairs(b) do res[k] = true end
    return res
end

function Set.intersection(a,b)
    local res = Set.new{}
    for k,v in pairs(a) do
        if b[k] then
            res[k] = true
        end
    end
    return res
end

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

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


--将元方法加入元表
mt.__add = Set.union   --指定加号为求并集的方法
mt.__mul = Set.intersection  --指定乘号为交集的方法

s1 = Set.new{11,22,31,44,56}
s2 = Set.new{66,33,22,31}
s3 = s1 + s2 --求并集   
Set.print(s3) --输出 {11, 31, 66, 22, 33, 56, 44}
s4 = s1 * s2 --求交集
Set.print(s4) --输出 {31, 22}

13.2 关系类元方法

关系是指 __eq(等于)、__lt(小于)等

mt.__le = function(a,b)
     for k in pairs(a) do
        if not b[k] then  return false end
     end
     return true
end

mt.__lt = function(a,b)
    return a<=b and not (b<=a)
end

mt.__eq = function(a,b)
    return a<=b and b<=a
end

ss1 = Set.new{2,4}
ss2 = Set.new{4,10,2}
print(ss1<=ss2)  --true
print(ss1<ss2)   --true
print(ss1>=ss1)  --true
print(ss1>ss1)   --false
print(ss1 == ss2*ss1)  --true

13.3 库定义的元方法

tostring是一个典型的实例。它能将各种类型的值表示为简单的文本格式

print({}) ----table: 003ECEF0

函数总是调用tostring来格式化输出。当格式化任意值时,tostring会检测该值是否有一个 __tostring元方法。如果有,他就调用这个方法用来作为tostring的返回值

在集合实例中,我们定议了将任命表示为字符串的方法,我们可以设置元表的__tostring字段

mt.__tostring = Set.tostring
sstext = Set.new{33,55,6666}
print(sstext)  --{55, 33, 6666}

假设想要保护集合的元表,使用户即不能看也不能修改集合的元表。那么就需要用到__metatable。当设置了该字段时,getmetatable就会返回这个字段的值,而setmetatable会引发一个错误

mt.__metatable = "not your business"
sstext1 = Set.new{}  
print(getmetatable(sstext1))  --not your business
setmetatable(s1,{})

13.4 table 访问的元方法

13.4.1 __index元方法

当访问一个table中不存在的字段中时,如果这个字段不存在得到nil,但是如果这个table有一个元方法__index那么如果没有这个字段,就由这个元方法来提供结果

Window = {}

Window.prototype = {x=0,y=0,width = 100,height = 100}
Window.mt = {}

function Window.new(o)
    setmetatable(o,Window.mt)
    return o
end

--现在定义一个元方法
Window.mt.__index = function(table,key)
    return Window.prototype[key]
end


w = Window.new{x=10,y=20}
print(w.width)  -- 100 window实际上没有width这个字段

__index元方法还可以是一个table

13.4.2  __newindex元方法

与__index不同的是__index是在查询的时候用的而_newindes是在更新的时候用的

13.4.3具有默认值的table

以下代码为table设置默认值

function setDefault(t,d)
    local mt = {__index = function() return d end}
    setmetatable(t,mt)
end

13.4.4 跟踪table的访问

__index和__newindex都是在table中没有所需的index才发挥作用。因为只有table保持空才能捕捉到所有对他的访问,为了监视一个table的所有访问就得为真正的 table 创建一个代理

 

t_src = {}  --要跟踪的表
local _t = t_src

t = {} --创建代理

--创建元表
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
    end
}
setmetatable(t,mt)

t[2]  = "hello"  -- *update of element 2 to hello
print(t[2])  --*access to element 2

13.4.5 只读的table

只读table与上一节跟踪table类似,是通过__newindex来限制修改table内存


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Lua修改文件名发布时间:2022-07-22
下一篇:
lua闭包发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap