lua(6)-元表(metatable)和元方法(meatmethod)

来源:互联网 发布:淘宝分享有礼活动 编辑:程序博客网 时间:2024/05/15 07:29

通常,Lua中的每个值都有一套预定义的操作集合。例如,可以将数字相加,可以连接字符串。但是我们无法将两个table相加,无法对函数作比较,也无法调用一个字符串。因此可以通过元表来修改一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。例如,假设a和b都是table,通过元表可以定义如何计算表达式a+b。当Lua试图将两个table相加时,它会先检查两者之一是否有元表,然后检查该元表中是否有一个叫__add的字段。如果Lua找到了该字段,就调用该字段对应的值。这个值也就是“元方法”,它应该是一个函数。

简单地说,元表和元方法就类似于C++、Java等语言中的操作符重载。

默认情况下,除了table和userdata类型,其他的类型默认共享一个元表,因此可以实现诸如以下的这些操作

       local a = 1

       local b = 2

       c = a+b

       local d ="s1"

       local e ="s2"

       local f = d..e

 

在lua代码中声明和定义一个变量的时候,不会为这个变量创建一个新的元表,因此如果想实现两个table相加或相减等操作,得为这两个table设置一个元表,同时赋予相加操作的元方法。例如,重载"+"操作符,使其能允许两个table相加,则需重写元表中的__add方法,重写了__add元方法后,当使用”+”操作符时,如果操作数经过tonumber为nil,如果不为nil,则调用原生的”+”号方法,如果为nil,则检查两个操作数中是否含有__add方法,如果有,则执行__add方法,否则抛出异常。除了算术类元方法__add,lua还提供了其余操作符号的元方法关键字,包括关系类元方法和库定义的元方法。

常用的元方法字段__add符号"+"的元方法(二元操作符,相加)
__sub符号"-"的元方法 (二元操作符,相减)
__mul符号"*"的元方法 (二元操作符,相乘)
__div符号"/"的元方法 (二元操作符,相除)
__mod符号"%"的元方法 (二元操作符,取余)
__pow符号"^"的元方法 (二元操作符,次幂)
__unm符号"-"的元方法(一元操作符,正负取反)
__concat符号".."的元方法 (二元操作符,连接)
__len符号"#"的元方法(一元操作符,取长度)
__eq符号"=="的元方法 (二元操作符,是否相等)
__lt符号"<"或">"的元方法 (二元操作符,小于)
__le符号"<="或">="的元方法 (二元操作符,小于等于)
__indextable元素的下标
__newindextable新分配元素的下标
__call调用一个变量
__tostring格式化为字符串
__metatable元表
__mode引用模式(用于设置一个值是否是弱引用)

(1)__add元方法。

__add元方法对应符号”+”。对于相加的两个操作数op1与op2,op1+op2会进行如下操作:

function add_event (op1, op2)       local o1,o2 = tonumber(op1), tonumber(op2)       if o1 ando2 then  -- (是否op1和op2都可转为数字?)              returno1 + o2   -- (这里的"+"代表原始的加法)       else  -- (如果有一个及以上的操作数不能转化为数字)              --(获取操作数的__add元方法)              localh = getbinhandler(op1, op2, "__add")              ifh then              --(调用这个__add元方法,并将两个操作数传递给这个元方法)                     return(h(op1, op2))              else  -- (如果没有元方法,抛出异常)                     error(···)              end       endend

以下示例重载__add方法实现两个table相加


输出



(2)__sub元方法。

__sub元方法对应符号”-”。

以下示例重载__sub方法实现两个table相减


输出


__mul、__div、__mod等算术类元方法的实现与(1)和(2)相似。


(3)__unm元方法。

__unm元方法是lua中正负数取反的元方法,对应一元操作符”-”,调用__unm元方法的逻辑原型如下:

function unm_event (op)       local o =tonumber(op)       if othen  -- (操作符是否是数字?)              return-o  -- (取反返回,这里的"-"是原始的取反方法)       else  -- (如果操作数不是数字)              -- (获取__unm元方法)              localh = metatable(op).__unm              ifh then              --(调用__unm元方法)                     return(h(op))              else  -- (抛出异常)                     error(···)              end       endend

以下示例重载__unm方法实现对一个table取反


输出



(4)__eq元方法。

__eq是lua中关系型的元方法,用于判断两个操作数是否相等,即”==”。lua提供的元方法字段中并没有"~="符号的元方法,因为lua代码被编译时将"~="转化成了not(a==b);同样的,a>b会转化为b<a,a>=b转化为b<=a。

关系型的元方法也会对传入元方法的多个元素进行自己的操作。

__eq示例判断两个table是否相等。


输出



(5)__tostring元方法。

以上的元方法都是基于操作符号,lua中还有基于关键字的元方法,这种元方法叫做库定义元方法。比如我们使用print(value)关键字的时候,总能将value的类型格式化成符合print关键字语法的类型,print关键字在使用时会在value的元表中查找__tostring元方法,类似于__tostring就是库定义元方法。

我们来对table进行库定义方法的修改

__tostring示例打印一个table。


输出



(6)__concat元方法。

__concat元方法是lua中字符串连接的方法,对应二元操作符”..”,调用__concat元方法的逻辑原型如下:

function concat_event (op1, op2)       if(type(op1) == "string" or type(op1) == "number") and              (type(op2)== "string" or type(op2) == "number") then              returnop1 .. op2  -- (这里的".."是原始的字符串连接操作)       else              localh = getbinhandler(op1, op2, "__concat")              ifh then                     return(h(op1, op2))              else                     error(···)              end       endend

以下示例重载__concat元方法实现连接两个table的字符串。


输出



(7)__len元方法。

__len元方法是lua中取操作符长度的方法,对应一元操作符”#”,调用__len元方法的逻辑原型如下:

function len_event (op)       if type(op) == "string" then              return strlen(op)         -- (原始的取字符串长度)       elseif type(op) == "table" then              return #op                -- (原始的取table长度)       else              local h = metatable(op).__len              if h then                     return (h(op))              else                     error(···)              end       endend


(8)__lt元方法。

__lt元方法是lua中“小于”的方法,对应一元操作符”<”,调用__lt元方法的逻辑原型如下:

function lt_event (op1, op2)       if type(op1) == "number" and type(op2) =="number" then              return op1 < op2  -- (对数字进行原始的"<"操作)       elseif type(op1) == "string" and type(op2) =="string" then              return op1 < op2  -- (对字符串进行原始的"<操作")       else              local h = getcomphandler(op1, op2, "__lt")              if h then                     return (h(op1, op2))              else                     error(···)              end       endend

以下示例重载__lt元方法实现两个数字table的大小比较。


输出



(9)__le元方法。

__le元方法是lua中“小于等于”的方法,对应一元操作符”<=”,其函数原型与重写方法与__lt类似。


(10)__index元方法。

__index元方法用于索引一个table的元素。对于table[key],如果table[key]为nil,则会调用__index元方法,如果__index返回的值为nin,table[key]才会返回nil。

调用__index元方法的逻辑原型如下:

function gettable_event (table, key)       local h       iftype(table) == "table" then              localv = rawget(table, key)          -- (用原始的方法获得table[key]值)              ifv ~= nil then return v end              h =metatable(table).__index               --(如果table[key]为空,调用__index)              ifh == nil then return nil end       else              h =metatable(table).__index              ifh == nil then                     error(···)              end       end       if type(h)== "function" then              return(h(table, key))     -- (如果__index是function类型,调用它)       elsereturn h[key]           -- (如果__index是table类型,将索引__index[key]的值)       endend

__index元方法可以是函数,也可以是一个table。以下示例一个面向对象的例子,这里__index是一个函数,假设有People这个类,People里面含有name、age这些变量,含有SayHello、New这些成员方法,创建People的两个实例对象XiaoMing和LiHua,然后调用这两个实例对象中不存在的name、age、SayHello、New等字段。


输出


当__index元方法是一个table的时候,table[key]如果为nil,则会去查找__index[key]。以下示例当__index是一个table的时候的用法。


输出


如果在访问一个table时,不想触发它的__index元方法,可以使用函数rawget(t,i)来获取table中元素的值。


输出



(11)__newindex元方法。

__newindex元方法与__index元方法类似,只是__index用于索引一个table的值,而__newindex用于table的赋值,也就是table[key] = value的时候,如果table[key]不存在,则会查找__newindex元方法,如果__newindex不为nil,则调用__newindex元方法。

调用__newindex元方法的逻辑原型如下:

function settable_event (table, key, value)       local h       iftype(table) == "table" then              localv = rawget(table, key)              ifv ~= nil then rawset(table, key, value); return end              h =metatable(table).__newindex              ifh == nil then rawset(table, key, value); return end       else              h =metatable(table).__newindex              ifh == nil then                     error(···)              end       end       if type(h)== "function" then              h(table,key,value)           -- (如果__newindex是function,调用它)       elseh[key] = value             -- (如果__newindex是table,索引__newindex[key])       endend

以下示例当__newindex为函数时的用法。


输出



(12)__call元方法。

__call元方法是“调用”的方法,对应符号”()”,当在一个对象的后面使用调用,lua首先会判断这个对象是否是function类型,如果是则按照执行函数的形式执行这个对象;如果这个对象不是function类型,则调用__call方法。

调用__call元方法的逻辑原型如下:

function function_event (func, ...)       iftype(func) == "function" then              returnfunc(...)   -- (原始的调用)       else              localh = metatable(func).__call              ifh then                     returnh(func, ...)              else                     error(···)              end       endend

以下示例__call元方法的用法,实现当调用一个table时,打印它的参数。


输出



(13)__mode元方法。

__mode是lua用于实现table的弱引用的元方法(可以参考lua(5)-table(表)这篇文章),每一次lua的内存回收都会检测该table内是否含有__mode元方法,当一个table的__mode元方法被声明并定义后,内存回收将会清除table被标记为“垃圾”的对象。

以下示例__mode元方法的使用


输出



0 0
原创粉丝点击