• lua编程之元表与元方法


    一、 前言

    lua是一种非常轻量的动态类型语言,在1993年由由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo等人发明,lua的设计目标是轻便地嵌入宿主语言,增强系统的可扩展性和可定制性。lua的源码只有两万余行,非常精简小巧,在目前的脚本引擎中,lua的速度是最快的,这也是lua进入程序设计语言前20名,如今已经广泛应用于游戏行业,这几篇文章将会讨论下lua的几个比较重要的特性。

    一门语言的类型系统是其最根本的特征,所以本文先从与lua的类型系统关系最紧密的元表和元方法谈起。作为一门轻量级语言,lua的核心非常精简,它的基本类型只有8种:nil,boolean,number,string,userdata,function,thread和table,其中table是唯一的数据结构,是lua中最重要的类型,可以作为其他数据结构的基础,如数组,链表,队列和集合等都可以通过table实现。更强大的是,lua还为table提供了自定义操作的功能。在c++等面向对象语言中,类的可操作行为由成员函数决定。lua中,元方法就是table的“成员函数”,为不同的table提供特殊的操作行为,元表是元方法的集合。通过元表和元方法,table可以直接实现类,继承等面向对象特性。

    二、 元表元方法介绍

    lua中每个值其实都有元表,不过每个table和userdata都可以有自己专有的元表,(userdata是宿主中的数据结构,可以使用宿主语言的方法,为了限制过度对其使用元表,不能在lua脚本中直接设置,需通过lua_setmetatable创建,这里不讨论),而其他类型的预定义操作都在一个共享的元表中,新的table默认没有元表,必须通过setmetatable和getmetatable设置和查询元表。

    t = {}
    assertgetmetatable(t)== nil)  
    t1 = {}
    setmetatable(t, t1)
    assert(getmetatable(t) == t1)

    在元表中定义的函数就是元方法,table的元方法分为算数类,关系类,库定义和访问类的元方法。

    1. 算数类元方法

    lua的算数类元方法都有对应的字段名,包括__add, __mul,__sub, __div,__mod和__pow等,下面示例了如何定义两个table的加法操作,

    a = { "a1", "a2","a3" }
    b = { "b1", "b2", "b3" }
    meta = { }
    meta.__add = function(t1, t2)
      t = { }
      for k,  v in ipairs(t1) do
        table.insert(t, v)
      end
      for k, v in ipairs(t2) do
        table.insert(t, v)
      end
      return t
    end
    setmetatable(a, meta)
    c = a+b
    for _,v in ipairs(c) do
      print(v)
    end

    上面代码中只需要给表a设置了元表,表b没有元表也能正常运行,这与lua查找元表的顺序有关系。lua先查找第一个table,如果有元表并且其中有 __add方法就调用该方法,不关心第二个table有没有元表;否则查找第二个table有没有__add的元方法,有就调用第二个table的元方法;如果都没有这个元方法就引发一个错误。

    2. 关系类元方法

    关系类元方法只有等于__eq,小于__lt和小于等于__le这3个操作,其他3个会自动转化,如a>b会自动转为b<a.

    a = { "a1", "a2" }
    b = {  "b1", "b2", "b3" }
    meta = { }
    meta.__le = function(a, b) 
      for k in pairs(a) do
        if not b[k] then return false end
      end
      return true
    end
    meta.__lt = function(a, b)
      return a<=b and not (b<=a)
    end
    meta.__eq = function(a, b)
      return a<=b and b<=a
    end
    setmetatable(a, meta)
    setmetatable(b, meta)
    print(a<=b)
    print(a<b)
    print(a>=a)
    print(b>a)
    print(b<a)

    与算法类元方法不同,table必须具有相同的元方法才能用于比较操作。

    3. 库定义的元方法

    上面的元方法都是lua核心具有的,是lua虚拟机定义的,除此之外,各种程序库也会用自己的字段定义元方法,比如print总是调用table的tostring方法,

    a = { "a1", "a2" }
    meta.__tostring = function(a)
      local l = { }
      for _,k in pairs(a) do
        l[#l+1] = k;
      end
      return "{"..table.concat(l, ",").."}"
    end
    setmetatable(a, meta)
    print(a)

    4. 访问类元方法

    访问元方法使用最普遍的是__index和__newindex。一般当访问一个table中不存在的元素时会返回nil,但是如果table具有__index元方法,就不返回nil而是调用这个元方法。利用__index可以方便地实现继承,

    mt = { }
    mt.__index = function(t, k)
      return base[k]
    end
    
    base = { b1 = 1, b2 = 2, b3 = 3 }
    derive = { d = 4 }
    
    setmetatable(derive, mt)
    
    print(derive.b1)
    print(derive.d)

    当对table中不存在的索引赋值时就会调用__newindex元方法,

    mt = { }
    mt.__newindex = function(t, k, v)
      base[k] = v
    end
    base = { b1 = 1, b2 = 2, b3 = 3 }
    derive = { d1 = 4 }
    setmetatable(derive, mt)
    derive["d2"] = 5
    print(base.d2)

    三、 元表元方法实例

    下面是一个使用元方法的实例,用于产生迭代递增表,

    T = { container = { } }
    
    T.mt = {
    
      __add = function(a, b)
        local c = T.new{}
        for k,v in pairs(T.new(a)) do
          c[k] = v
        end
        for k,v in pairs(T.new(b)) do
          c[k] = v
        end
        return c
      end,
    
      __sub = function(a, b)
        local c = T.new{}
        for k,v in pairs(T.new(a)) do
          c[k] = v
        end
        for k,v in pairs(T.new(b)) do
          c[k] = nil
        end
        return c
      end,
    
      __tostring = function(a)
        local l = { }
        for k in pairs(a) do
          l[#l+1] = k;
        end
        return "{"..table.concat(l, ",").."}"
      end
    }
    
    T.new = function(t)
      if (t == nil) then t = {} end
      if (getmetatable(t) == T.mt) then return t end
     local r = {}
      for _, b in ipairs(t) do
        r[tostring(b)] = true
      end
      setmetatable(r, T.mt)
      return r
    end
    
    T.print = function(t) 
       for k, v in pairs(t.container) do
         print(k)
         print(v)
       end
     end
    
    local mt = {
      __newindex = function(t, k, v)
        t.container[k] = T.new(v)
      end,
    
      __index = function(t, k)
        return t.container[k]
      end,
    }
    
    setmetatable(T, mt)
    
    T["first"] = { "a1", "b1"}
    print("elements in table first")
    T.print(T)
    T["second"] = T["first"] + { "a2", "b2", "a3", "b3"}
    print("elements in table first and  second")
    T.print(T)
    T["third"] = T["second"] - { "a3", "b3" }
    print("elements in table first, second and third")
    T.print(T)
  • 相关阅读:
    Quartus 自定义一个IP核,通过AXI总线被HPS系统控制(未完待续)
    IR 发送数据的上升沿和下降沿的判断(边沿检测)
    机器学习特征工程和优化方法
    最大期望算法(EM)
    主题模型(Topic Model)
    马尔科夫(Markov)
    【mysql-02-2】使用文档-建表约束
    【mysql-02-3】使用文档-范式
    【mysql-02-1】使用文档-基本语法
    Linux身份鉴别机制原理
  • 原文地址:https://www.cnblogs.com/coderkian/p/4051165.html
Copyright © 2020-2023  润新知