如何简单易懂的理解lua的元表metatable

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何简单易懂的理解lua的元表metatable相关的知识,希望对你有一定的参考价值。

参考技术A Lua 的 metatable 并不只是为了 OO 而存在,但 Lua 的面向对象机理,prototype based object orientation,的确是通过 metatable 而实现的。

简单来说,prototype based OO 的根本特征是不采用「类」这一层抽象,也就是说,所有对象都是没有 class 的 instance。当一个 object 需要某个其他 object 已有的功能时,可以将后者设为自己的 prototype;同一个 object 可以同时作为几个其他 object 共同的 prototype(但反之通常不成立);已指定 prototype 的 object 还可以成为其他 object 的 prototype;一个 object 可以以自己为 prototype。一系列 object 以此方式组成一串 prototype 链,在某个 object 上调用它并未定义的方法或属性时,系统会上溯这条链,依次查看每一级 prototype 是否有定义,直至找到或穷尽到链条终点为止。举例而言,假设有三个维京人,甲会弹弓,乙会用剑,丙会持盾牌;甲给乙当 prototype,于是乙也会了弹弓;乙再给丙当 prototype,丙就成了可以持盾挥剑射弹弓的全能维京人。甲没有 prototype(或者有一个什么都不会的缺省 prototype),丙则不是任何人的 prototype(但他可以是)。
具体到 Lua 这里,这种「向上寻找 prototype」的机制,可以用 metatable 之中的 metamethod 完成。最简单但并不是最简洁的例子:
a = foo = function() return 'foo' end

b = bar = function() return 'bar' end

c = __index = b

setmetatable(a, c)
a.bar()
-- 'bar'

将 table c 的 __index 定义为 table b,然后将 a 的 metatable 定为 c,就让 b 变成了 a 的 prototype。而 c 作为 metatable 的角色其实可以融合到 b 或者 a 中:
a = foo = function() return 'foo' end

b = bar = function() return 'bar' end

b.__index = b

setmetatable(a, b)
a.bar()
-- 'bar'

这样一来,b 既是 a 的 prototype,也是 a 的 metatable。将 __index 指向另一个 table 大概是它最简单也最普遍的用法,只是 Lua 官方教程中引入 __index 的部分写得比较复杂,把它写成了一个函数,意图大概是考虑到大部分读者对 prototype based OO 并不是很了解(那时候 javascript 还没有现在这么流行),所以用 metatable 来强行实现了一个看起来很接近 class-based 的 OO。
参考技术B 2、老罗拉了一车梨, 参考技术C 八、让中、英文输入法智能化地出现

Lua 的元表

简述

元表指的是 Lua 中的 MetaTable,它提供了一种重定义任意一个 Lua 中对象或值默认行为的公开入口,让 Lua 也能像许多面向对象语言一样实现操作符或方法的重载。

 

原理解析

在 Lua 中,每种类型的数据都有其默认的操作,如:number 有加减操作,string 的拼接操作,function 的调用操作,这些都是该类型的默认操作,而我们可以通过修改其原来来实现对这些默认操作进行修改。

 

元表(MetaTable)和元方法

用来定义对 table 或 userdata 操作方式的表,例如通过元表来定义 table 相加 "+" 操作(类似与 C 语言中的运算符重载)

 

local mt = 
-- 合并两个表
mt.__add = function(t1, t2)
	local temp = 
    for _,v in pairs(t1) do
        table.insert(temp,v)
    end
    for _,v in pairs(t2) do
        table.insert(temp,v)
    end
    return temp
end
local tb1 = 1, 2
local tb2 = 3
-- 为 tb1 设置元表为 mt
setmetatable(tb1, mt)

local tb3 = tb1 + tb2

如上,当 tb1 + tb2 时,会调用 tb1 的元表中的 __add 元方法来计算结果。具体步骤如下:

  • 检查 tb1 是否有元表,若有,则查看其元表中是否有 __add 元方法,若有则调用;

  • 检查 tb2 是否有元表,若有,则查看其元表中是否有 __add 元方法,若有则调用;

  • 若都没有,则会报错。

因此只需要 tb1 或 tb2 其中一个有元表且具有 __add 元方法即可。

元方法: 上述 __add 就叫做 元方法,常见的元方法还有:

函数描述
__add运算符 +
__sub运算符 -
__mul运算符 *
__ div运算符 /
__mod运算符 %
__unm运算符 -(取反)
__concat运算符 ..
__eq运算符 ==
__lt运算符 <
__le运算符 <=
__call当函数调用
__tostring转化为字符串
__index调用一个索引
__newindex给一个索引赋值

 

1. __index 元方法

  • 作为函数时:将表与索引传入 __index 元方法,并 return 一个返回值
    
    local mt = 
     --第一个参数是表自己,第二个参数是调用的索引
    mt.__index = function(t,key)
         return "it is "..key
     end
     t = 1,2,3
     --输出未定义的key索引,输出为nil
     print(t.key)
     setmetatable(t,mt)
     --设置元表后输出未定义的key索引,调用元表的__index函数,返回"it is key"输出
     print(t.key)
    结果:
    nil
    it is key

     

  • 作为 table 时:查找 __index 元方法表,若有该索引,则返回该索引对应的值,否则返回 nil
    
    local mt = 
     mt.__index = key = "it is key"
    ​
     t = 1,2,3
     --输出未定义的key索引,输出为nil
     print(t.key)
     setmetatable(t,mt)
     --输出表中未定义,但元表的__index中定义的key索引时,输出__index中的key索引值"it is key"
     print(t.key)
     --输出表中未定义,但元表的__index中也未定义的值时,输出为nil
     print(t.key2)
    结果:
    
    nil
    it is key
    nil

     

2. __newindex 元方法

当为 table 中一个不存在的索引赋值时,会调用元表中的 _newindex 元方法

  • 作为函数时:会将赋值语句中的表、索引、赋的值当做参数去掉用(不对表进行改变)

    local mt = 
     --第一个参数时表自己,第二个参数是索引,第三个参数是赋的值
    mt.__newindex = function(t,index,value)
         print("index is "..index)
         print("value is "..value)
     end
    ​
     t = key = "it is key"
     setmetatable(t,mt)
     --输出表中已有索引key的值
     print(t.key)
     --为表中不存在的newKey索引赋值,调用了元表的__newIndex元方法,输出了参数信息
     t.newKey = 10
     --表中的newKey索引值还是空,上面看着是一个赋值操作,其实只是调用了__newIndex元方法,并没有对t中的元素进行改动
     print(t.newKey)
    结果:
    
    ​​​​​​​it is key
    index is newKey
    value is 10
    nil

     

  • 作为 table 时:为原 table 中不存在的索引赋值会将该索引和值赋到 __newindex 所指向的表中,不对原 table 进行改变

    local mt = 
     --将__newindex元方法设置为一个空表newTable
    local newTable = 
     mt.__newindex = newTable
     t = 
     setmetatable(t,mt)
     print(t.newKey,newTable.newKey)
     --对t中不存在的索引进行负值时,由于t的元表中的__newindex元方法指向了一个表,所以并没有对t中的索引进行赋值操作将,而是将__newindex所指向的newTable的newKey索引赋值为了"it is newKey"
     t.newKey = "it is newKey"
     print(t.newKey,newTable.newKey)
    结果:
    nil nil
    nil it is newKey

     

3. rawget 和 rawset

  • rawget 可以直接获取表中索引的真实值,而不通过 __index 元方法

    local mt = 
    mt.__index = key = "it is key"
    t = 
    setmetatable(t,mt)
    print(t.key)
    --通过rawget直接获取t中的key索引
    print(rawget(t,"key"))
    结果:
    
    ​​​​​​​it is key
    nil

     

  • rawset 可以直接为表中索引赋值,而不通过 __newindex 元方法

    local mt = 
    local newTable = 
    mt.__newindex = newTable
    t = 
    setmetatable(t,mt)
    print(t.newKey,newTable.newKey)
    --通过rawset直接向t的newKey索引赋值
    rawset(t,"newKey","it is newKey")
    print(t.newKey,newTable.newKey)
    结果:
    nil nil
    it is newKey  nil

     

 

运用场景

  • 通过为 table 设置元表,可以在 lua 中实现面向对象编程

  • 通过对 userdata 和元表,可以实现 lua 中对 c 中的结构进行面向对象式的访问

 

参考:

以上是关于如何简单易懂的理解lua的元表metatable的主要内容,如果未能解决你的问题,请参考以下文章

lua中的元表---metatable

lua中的元表---metatable

Lua 元表(Metatable)

lua的元表与元方法

lua元表(metatable)

lua 元表操作