[Lua]关于lua元表(Metatable)

前言

在lua中最重要的一个数据结构就是table,而table有一个重要的功能就是设置元表(Metatable)在元表中设置元方法,就能帮我们实现面向对象的一些功能。

基础概念

lua中每个值都可以有一个元表,这个元表就是一个普通的table,它用于定义原始值在特定操作下的行为。如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。
在元表的事件的键值是一个双下划线(__)加事件名的字符串;键关联的那些值被称为元方法。
可以用getmetatable函数来获取任何值的元表。lua使用直接访问的方式从元表中查询元方法。
可以使用setmetatable来替换一张表的元表,在lua中,你不可以改变表以外其他类型的值的元表;如果想改变这些非表类型的值的元表,需要是用c语言接口。
table和userdata有独立的元表,其他类型的值按类型共享元表;也就是说所有的数字都共享同一个元素,所有的字符串共享另一个元表等等。在默认情况下,值是没有元表的,但是字符串库在初始化的时候为字符串类型设置了元表。
元表决定了一个对象在数学运算,位运算,比较,连接,取长度,调用,索引时的行为。元表还可以定义一个函数,当table或者userdata被回收的时候调用它。

事件

  • add:+操作。如果任何不适数字的值做加法,lua都会尝试调用元方法。首先,lua检查第一个操作数,如果这个操作数没有为“add”事件的元方法,lua就会检查第二个操作数,一旦lua找到了元方法,它就将这两个操作数作为参数传入元方法里然后返回一个操作结果。如果找不到元方法,将抛出一个错误
local table1 = {
    [1] = 10
}
local table2 = {
    [1] = 20
}
--[
    a为+左边的,b为右边的值
--]
local mt = {
    __add = function(a,b)
        print(a[1],b[1])
    end
}
setmetatable(table2,mt)
local table3 = table1 + table2
  • __sub:-操作符。行为和“add”操作类型。

  • __mul:*操作符。行为和“add”操作类似。

  • __div:/操作符。行为和“add”操作类似。

  • __mod:%操作符。行为同上。

  • __pow:^(次方)操作符,行为同上

  • __unm:-(取反)操作,行为同上

  • __idiv://(向下取整除法)。行为同上

  • __band:&(按位与)操作。行为同上。lua会在任何一个操作数无法转换为整数时尝试取元方法

  • __bor:|(按位或)操作,行为同上。

  • __bxor:~(按位异或)操作,行为同上。

  • __bnot:~(按位非)操作,行为同上。

  • __shl:<<(左移)操作,行为同上。

  • __shr:>>(右移)操作,行为同上。

  • __concat:..(连接)操作,行为和“add”操作类似,不同的是lua在任何操作数即不是一个字符串,也不是一个数字的情况下尝试元方法。

  • __len:#(取长度)操作,如果对象不是字符串,lua会尝试它的元方法。如果有元方法,则调用它并将对象以参数形式传入,而返回值则作为结果。如果对象是一张表且没有元方法,lua使用表的取长度操作。其他情况均抛出异常

  • __eq:==(等于)操作。和“add”行为类似,不同的是lua仅在两个值都是表或都是userdata且他们不是同一个对象的时候才尝试元方法。返回结果转换为bool。

  • __lt:<(小于)操作。和“add”操作行为类似,不同的是lua仅在两个值不全为整数也不全为字符串时才尝试元方法。返回结果为bool。

  • le:<=(小于等于)操作。和其他操作不同,小于等于操作可能用到两个不同的事件。首先回去找两个操作数中的"le"元方法,如果都没有,那就会再次查找"__lt"。返回bool

  • __index:索引table[key]。当table不是表或者是表中不存在key这个键时,这个时间会被触发。这个事件的元方法可以是一张表,也可以是一个函数,如果是一个函数的话则以table和key作为参数调用它。如果是一个表,那就是在这个表里去key这个索引的值。

  • newindex:索引赋值table[key] = value.和索引事件类似,它发生在table不是表或是表table中不存在key这个键的时候调用对应的元方法
    这个方法可以是一个表也可以是一个函数,如果是函数的话则是以table,key和value作为参数传入。如果是一张表,则对这个表做索引赋值操作。一旦有了“
    newindex”元方法,lua就不再做最初的赋值操作。

  • __call:函数调用操作func(args)。当lua尝试调用一个非函数的值的时候会触发这个事件。查找func的元方法,如果找的到,func作为第一个参数传入,原来调用的参数(args)后依次排在后面。

方法

getmetatable(object)

这个方法是用来获取一个值的元表。否则,如果在该对象的元表中有“__metatable”域时返回其关联值,没有时返回该对象的元素。

setmetatable(table,metatable)

这个方法是用来设置一个值的元表。(不能在lua中改变其它类型的元表,只能在c中做),如果metatable是nil,则将table中的元表移除。如果元表有“__metatable”域,抛出一个错误。

rawequal(v1,v2)

在不触发任何元方法的情况下检查v1是否和v2相等,返回一个bool值

rawget(table,index)

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

rawlen(v)

在不触发任何元方法的情况下返回对象v的长度。v可以是表或字符串。它返回一个整数。

rawset(table,index,value)

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

实战

不能修改tale中的值

local list = {
    ["a"] = "a",
    ["b"] = "b"
}
local mt = {
    __index = list,
    __newindex = function (table,key,value)
        if list[key] then
            print("不能修改")
        end
        end,
}
local list2 = {}
local list1 = setmetatable(list2,mt)
list1["a"] = "c"

把list作为元表赋值给list1,当我要给list1赋值的时候,list1中表是空的,所以会去找元方法中的newindex,newindex函数的参数table代表着list2,key代表着“a”,value代表着“c”,做判断的时候,我们直接函数里面不处理,就不会添加到我们不想修改的list中的值了。

重点

有的时候,我们不想在lua中被设置了一个全局变量。因为lua其实也相当于一个表,它的全局变量存在_G中,首先我们可以先获取_G,然后给_G设置一个不能添加修改值的这个方法。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注