• 一文读懂Lua元表


    元表

    Lua语言中的每种类型的值都有一套可预见的操作集合。例如,我们可以将数字相加,可以连接字符串,还可以在表中插入键值对等,但是我们无法将两个表相加,无法对函数作比较,也无法调用一个字符串,除非使用元表。

    元表可以修改一个值在面对一个未知操作时的行为。例如,假设a和b都是表,那么可以通过元表定义Lua语言如何计算表达式a+b。当Lua语言试图将两个表相加时,它会先检查两者之一是否有元表(metatable)且该元表中是否有__add字段。如果Lua语言找到了该字段,就调用该字段对应的值,即所谓的元方法(metamethod)(是一个函数)。

    Lua语言中的每一个值都可以有元表。每一个表和用户数据类型都具有各自独立的元表,而其他类型的值则共享对应类型所属的同一个元表。

    获取元表

    获取元表使用getmetatable()方法

    t = {}
    print(getmetatable(t)) --> nil

    设置元表

    可以使用函数setmetatable来设置或修改任意表的元表

    t1 = {}
    setmetatable(t,t1)
    print(getmetatable(t) == t1) --> true

    我们只能为表设置元表;如果要为其他类型的值设置元表,则必须通过C代码或调试库完成。字符串标准库为所有的字符串都设罝了同一个元表,而其他类型在默认情况中都没有元表:

    print(getmetatable("hello world"))   --> table: 0000022E8BA91B40
    print(getmetatable(123)) --> nil
    print(getmetatable(print)) --> nil

    为两个表添加算术运算功能

    下面展示怎么为两个表添加 “+” 功能

    -- 定义两个表 t 和 t1
    t = {}
    t1 = {}

    -- 向表中添加变量并赋值
    t.a = 123
    t1.a = 4

    -- 向两个表添加函数
    t.func = function ()
       return 1
    end

    t1.func = function ()
       return 2
    end

    -- 定义元表
    mt = {}

    -- 分别为两个表设置元表
    setmetatable(t,mt)
    setmetatable(t1,mt)

    --为元表定于__add函数
    --a b 分别代表执行加法的表,这里指代t和t1
    mt.__add = function (a,b)
       print(a.func() + b.func())   --> 3
       return a.a + b.a
    end

    print(t + t1) --> 127

    Lua语言会按照如下步骤来查找元方法:

    • 如果第一个值有元表且元表中存在所需的元方法,那么Lua语言就使用这个元方法,与第二个值无关

    • 如果第二个值有元表且元表中存在所需的元方法,Lua语言就使用这个元方法

    • 否则,Lua语言就抛出异常。

    因此 在执行最后一行 t + t1的时候,会检查元表中是否存在 t1 中是否存在 __add 方法,如果存在,则调用该元方法,否则查找 t2,如果还是不存在,将会抛出异常。因此上面的代码中,这行代码 setmetatable(t1,mt) 可以删除,因为始终会执行 t 中的方法。例如我们修改上面代码

    -- 定义第二个元表
    mt1 = {}

    -- 注释t的元表,为t1添加新定义的元表
    --setmetatable(t,mt)
    setmetatable(t1,mt1)

    mt1.__add = function()
       print("this is mt1 add")
    end

    print(t + t1)  -->   调用mt1.__add方法,执行方法中的print语句
     -->   打印nil,因为mt1.__addm

    除了 __add 外,还有下面这些键值定义:

    描述
    __add 改变加法操作符的行为。
    __sub 改变减法操作符的行为。
    __mul 改变乘法操作符的行为。
    __div 改变除法操作符的行为。
    __mod 改变模除操作符的行为。
    __unm 改变一元减操作符的行为。
    __concat 改变连接操作符的行为。
    __eq 改变等于操作符的行为。
    __lt 改变小于操作符的行为。
    __le 改变小于等于操作符的行为。

     

    表相关的元方法

    __index元方法

    当我们访问表中一个不存在的字段时,得到的结果会是nil,这是正确的,但不是完整的真相。实际上,这些访问会引发解释器查找一个名为 __index 的元方法。如果没有这个元方法,那么像一般情况下一样,结果就是nil;否则,则由这个元方法来提供最终结果。

    mt = {x = 5}    --定义一个元表,里面拥有一个字段x

    w = {}

    w = setmetatable(w,mt) --将mt设置为w的元表

    mt.__index = mt --设置元表的__index值

    print(w.x) -- 5
    print(w.y) -- nil

    --将mt的__index元方法设置为函数
    mt.__index = function(_,key)
       return mt[key]
    end

    print(w.x) --w中没有x字段,所以调用函数 __index,传入的参数为function(w,x),所以得到的值为mt["x"] = 5
     --也就是以表和键为参数调用该函数,并返回该函数的返回值

    Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

    • 在表中查找,如果找到,返回该元素,找不到则继续

    • 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。

    • 判断元表有没有 index 方法,如果 index 方法为 nil,则返回 nil;如果 index 方法是一个表,则重复 1、2、3;如果 index 方法是一个函数,Lua会以表和键为参数调用该函数,并返回该函数的返回值。

    如果我们希望在访问一个表时不调用__index元方法,那么可以使用函数rawget,它在不考虑元表的情况下对表进行简单的访问,定义为:

    rawget (table, index)

    在不触发任何元方法的情况下 获取 table[index] 的值。 table 必须是一张表; index 可以是任何值。

    接着上面的代码,添加一些内容

    print(rawget(w,"x"))    -- 忽略元表中的值,x在w表中不存在,所以输出为 nil
    w.x = "hhh"
    print(rawget(w,"x"))    -- 输出 hhh

     

    __newindex元方法

    元方法__newindex__index类似,不同之处在于前者用于表的更新而后者用于表的查询。当对一个表中不存在的索引赋值时,解释器就会查找__newindex元方法:如果这个元方法存在,那么解释器就调用它而不执行赋值。

    mt = {x = 5,y = 6}

    w = {}

    w = setmetatable(w,mt)
    mt.__newindex = mt

    print(mt.y)   -- 6
    w.y = 123    -- __newindex中包含y字段,所以为mt.y赋值,而不进行自身赋值
    print(w.y) --nil
    print(mt.y) --123

    如果我们想跳过原函数为它赋值,可以使用rawset方法

    rawset (table, index, value)

    在不触发任何元方法的情况下 将 table[index] 设为 valuetable 必须是一张表, index 可以是 nil 与 NaN 之外的任何值。 value 可以是任何 Lua 值。

    在上面的基础上添加下面代码

    rawset(w,"y",456)
    print(w.y) -- 456
    print(mt.y) -- 123

     

  • 相关阅读:
    c读取文本文档
    java类中定义接口
    android selector
    android listview
    android继承Dialog实现自定义对话框
    移植net-snmp到开发板(mini210)
    [BZOJ1901]Zju2112 Dynamic Rankings
    [BZOJ3524][Poi2014]Couriers
    [codeforces722D]Generating Sets
    [codeforces722C]Destroying Array
  • 原文地址:https://www.cnblogs.com/forever-Ys/p/15245136.html
Copyright © 2020-2023  润新知