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 中的结构进行面向对象式的访问

 

参考:

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页