前言

Lua本身没有面向对象的思想,但是可以根据表、元表、元方法来靠近它

一、元表与元方法的概念

Lua中每个值都可具有元表。元表是普通的Lua表,定义了原始值在某些特定操作下 的行为。例如,当table作为加法的操作数时,Lua检查其元表中的”__add”字段是否有 个函数。如果有,Lua调用它执行加法。我们称元表中的键为事件(event),称值为 元方法(metamethod)。
前述例子中的事件是”add”,元方法是执行加法的函数。

Lua创建新的table的同时不会创建元表
t={}
print(getmetatable(t)) –>nil 可以使用setmetatable(t,t1)给t设置元表为t1。
在Lua中,只能设置table的元表。
若要设置其他类型的元表,则必须通过C代码来完成。

元表可以想象成类,表是元表的子类
元表 :定义某些特定行为的操作 t1+t2 t1-t2
元表: 就是一个普通的表(table)t = {}
t1 + t2 : 程序会到t1 t2 中查找元表,再在元表中查找 __add
如果有 __add ,就会执行对应的方法,如果没有,程序报错
像 元表的__add方法就是元方法

二、设置元表

设置元表 setmetatable , 给表设置一个元表
获取元表 getmetatable ,得到元表的地址

local mt = {}
--Lua创建新的table的同时不会创建元表
t={}
print(getmetatable(t)) -->nil

local mt = {}
local t  = {}
setmetatable(t,mt) -- 给表t设置元表mt
print(mt) -- 打印地址 ,
print(getmetatable(t)) --获取元表 打印元表地址,它们的地址相同
三、元方法 __add

表之间可以进行运算符计算,但是需要表有元表,元表有对应的元方法,否则报错
并且注意元方法使用了两个下划线作前缀

local mt = {}

mt.__add = function(Lhs,Rhs)
    print(Lhs)
    print(Rhs)
    return "abc"
end

local t1 = {}
local t2 = {}
setmetatable(t1,mt) 
setmetatable(t2,mt)

print("t1的地址:")
print(t1) 
print("t2的地址:")
print(t2) --t1 和 t2 的地址不同

local result = t1 + t2  --有在元表上写了 __add方法就可以表之间可相加,相加时调用元表
print(result) -- abc

__add方法可以看成是运算符重载的加法运算,里面传入左右两个元表,
最后只能返回一个(元表、表达式、值,不返回也可以),多了会以nil值返回

四、其他元方法

__add:加法
__sub:减法
__mul:乘法
__div:除法
__unm:相反数
__mod:取模
__pow:乘幂
具体的使用方式和add类似,不再详细叙述。

关系类的元方法

除了加法减法这些算术类的操作之外,大于小于等这些关系类的操作也是有元方法的:
__eq:等于
__lt:小于
__le:小于等于
如果对两个具备不同元表的值进行这些比较操作,就会报错,一定要注意,这和加减法
的规则不一样。

五、注意表和元表关系的区分

两个具有不同元表的值进行算术操作(比如加法)之前举例的时候,两个table相加, 这两个table都是具有相同的元表的,所以没有任何问题。
那么,如果两个table或者两个进行相加操作的值,具有不同的元表呢?
相同元表:执行元表中的元方法
不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,
如果没有就看第二个元表是否有元方法。
都没有元方法就会报错

local mt1 = {}
local mt2 = {}

mt1.__sub = function(t1,t2)
    print("mt1->sub")
end

mt2.__sub = function(t1,t2)
    print("mt2->sub")
end

local t1 = {}
local t2 = {}

setmetatable(t1,mt1) -- 设置mt1为t1(普通表)的元表
setmetatable(t2,mt2) -- 设置mt2为t2(普遍表)的元表

local result  = t1 - t2 --计算过程中调用元表的mt1.__sub元方法
print(result)  --nil

local result2 = t2 - t1  --计算过程中调用元表的mt2.__sub元方法
print(result2) --nil

我们再来理清下关系:
mt1 和 mt2 是元表 因为使用setmetatable方法设置了 setmetatable(t1,mt1) setmetatable(t2,mt2)
而 t1 和 t2 是普通的表,要使用普通的表来计算 ,而元方法在它们的元表中,在普通的表进行运算的时候元方法会(进行选择后)自动调用

六、__index 查询

如果访问表中不存在的字段->到元表中查找 __index
设想一下,当我们获取一个table中不存在的元素的时候。默认的返回nil,但是,如果我们不希望这样呢?我们希望在访问不存在的字段时,进行一些自定义的操作呢?没问题,Lua满足了我们,那就是,__index元方法。在使用加法操作时,会查找__add元 方法。那么,在调用table不存在的字段时,会调用__index元方法,这是一样的规则。

__index是一个函数
local mt = {}
mt.__index = function(table,key)
    print("元表中的index")
    print(table) -- 打印t表
    print(key) -- 打印name
end

local t = {}
setmetatable(t,mt)

print(t,mt) --可以找到table: 0x7f997bc07a70 table: 0x7f997bc04d50
print(t.name) --t表不可以找到,调用元表的__index函数,
__index是一个表
local k = {name = "Y" , age = 23 }
local mt = {
    __index = k-- index对应为表
}

local  t = {sex = "boy"}
setmetatable(t,mt)
print(t.sex) --可以在index对应表中找到就打印出值
print(t.name)
print(t.enjoy) -- 元表index对应的表中也不存在该字段,打印nil
表中内容带有函数
local k = {
    name = "Y",
    age = 23,
    SayHello = function()
        print("Hello")
    end,
    Bye = function()
        print("Bye")    
    end
}

local mt = {
    __index = k
}
local t = {}
setmetatable(t,mt) --设置mt为t的元表
print(t.name) --可以从元表的__idex对应的k表找到值,打印 Y
t.SayHello() -- 可以从元表的__idex对应的k表找到方法,调用打印出 Hello
t.Bye() -- 如果写错方法名(或元表也没有)则报错,这里可以找到,直接调用k表的Bye方法
七、__newindex的使用

有的时候我们要对table中某个不存在的字段赋值。没错,我们直接就能赋值了, 不会报错的。但是有的时候我想监控这个操作,如果有人想对table不存在的字段进 行赋值的时候,我想进行一些额外的处理呢?
这时候就要用到__newindex。

大家要记住这句话:__index用于查询,__newindex用于更新
查询找的是__index ,修改找的是__newindex

__newindex的规则:
a.如果__newindex是一个函数,则在给table不存在的字段赋值时,会调用这个函数。
b.如果__newindex是一个table,则在给table不存在的字段赋值时,会直接给__newindex的table赋值。
如果发现没有调用,可能是写错了,记得是要两个下划线~

普通表修改键值

存在则修改,不存在就会增加

local t = {name = "Y"}
t.name = "YY" --修改
t.age = 23

for key , value in pairs(t) do --无序遍历字典类型表
    print(key .. " : " .. value)
end

遍历打印得到(无序)

age : 23
name : YY

如上的 t.age 不存在,则会在表中增加一个

__newindex是一个函数

如果普通表没该变量,则元表会调用_newindex函数,但是不会给元表赋值

local mt = {
    __newindex = function(table,key,value)
    print(table)
    print(key)
    print(value)
    end
}
local t = {}
setmetatable(t,mt)
print(t.name) -- nil
t.name = "YY" 
print(t.name) -- 赋值后 先调用__newindex函数然后打印 nil
__newindex是一个表

如果普通表没有该变量,那么到元表查找,并在元表新增或修改变量值

local k = {name = "Y"}
local mt ={
    __newindex = k
}

local t = {}
setmetatable(t,mt)
print(t.name) -- nil  查询
print(k.name) -- Y    查询
print(t.name) -- nil  查询
t.name = "YY" -- 到对应的k表 修改
print(k.name) -- YY   查询  
print(t.name) -- nil  查询
__index 和 __newindex的使用
local smartman = {name = "none"}
local other = {name = "cc"}
local mt = {
    __index = smartman,
    __newindex = other
}
local t = {}
setmetatable(t,mt)
print("other赋值前的name = ".. other.name) --cc
print("t赋值前 : ".. t.name) -- 原表不变 none
t.name = "superman"
print("other修改后name = " .. other.name) --newindex对应的表数值修改 --superman
print("t修改后 : ".. t.name) -- 原表不变 -- none

smartman表对应__index只是用来查询,本身并没有变化,变化修改的是newindex对应的other表

八、忽略元表的操作rawget rawset
local mt = {
    __index = function (table,key)
        print("元表中的查找功能")
    end,
    __newindex = function()
        print("元表中的修改功能")
    end
}
local t = {}
setmetatable(t,mt)
print(t.name) -- nil 不忽略元表的功能
print(rawget(t,"name"))-- 查找 忽略元表的功能,不会调用元表的__index对应的方法
rawset(t,"name","YYY") -- 修改 忽略元表的功能,不会调用元表的__newindex对应的方法,
--即在t表添加name = "YYY"
print(t.name) --YYY