前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lua学习笔记:Lua里metatable元表的使用

Lua学习笔记:Lua里metatable元表的使用

原创
作者头像
晨星成焰
修改2024-09-13 07:55:09
1170
修改2024-09-13 07:55:09
举报
文章被收录于专栏:Lua学习笔记C++入门基础知识

元表简介

  • 元表: Lua 中的每个值都可以有一个 元表。 这个 元表 其实就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 "__add" 域下的函数。
  • 元表主要用于定义表的行为:例如如何处理索引不存在的情况、如何进行相关运算等。元表提供了一些特殊的字段(元方法),比 如 __index、__newindex、__add、__tostring 等。
    • 元表可以让一个基础的自定义数据类型 实现 内建行为(内建函数、运算符等)
    • 元表可以实现一个类
    • 元表可以看作一个普通表的 方法类,类似于C++中的纯虚类
  • 如何设置元表? 可以通过 setmetatable 函数给一个表设置元表,getmetatable 来获取任何值的元表
代码语言:lua
复制
local mt = {}  -- 创建元表
local t = {a=1, b=2}  -- 创建主体表

-- 尝试获取元表
local mt_got = getmetatable(t)

if mt_got == nil then
    print("The table does not have a metatable.")
else
    print("The table has a metatable.")
end
--输出 The table does not have a metatable.

setmetatable(t, mt)  -- 设置主体表的元表

local t = {a=1, b=2}

-- 尝试获取元表
local mt_got = getmetatable(t)

if mt_got == nil then
    print("The table does not have a metatable.")
else
    print("The table has a metatable.")
end
--输出 The table has a metatable.

元方法

讲到元方法就必须得提__index

1.__index

  • 索引key不存在时触发。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。此时,会读出 table 相应的元方法。这个事件的元方法可以是一个函数也可以是一张表。
    • 如果它是一个函数,则以 table 和 key 作为参数调用它。
    • 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。这个索引过程是走常规的流程,而不是直接索引,所以这次索引有可能引发另一次元方法的调用。
  • 总而言之:__index 是一个特殊的元方法,当尝试访问一个表中不存在的键时,Lua 会调用这个方法。这个方法可以用来提供默认值或者实现lua类继承行为。

1.函数调用

函数调用会返回函数的返回值(table表和key索引值会作为参数传递进去)

代码语言:lua
复制
-- MetatableTest.lua
local mt = {
    __index = function (t, key)
        print("正在寻找 "..key.."是否在元表里")
        if key == 'specialKey' then
            return "关键值"
        else
            return "未知值"
        end
    end
}

local t = {a=1, b=2}
setmetatable(t, mt)

print("t.specialKey:"..t.specialKey)  -- 输出:正在寻找 "..specialKey.."是否在元表里
                                      --       t.specialKey:关键值
print("t.C:"..t.C)  -- 输出: 正在寻找 C是否在元表里
                    --       t.C:未知值
print("t.a:"..t.a) -- 输出: t.a:1

2.表调用

  • 表调用Lua查找元素的规则如下:
    1. 在表中查找,找到则返回,找不到则继续
    2. 判断是否有元表,没有返回nil,有则继续
    3. 判断元表有无__index方法,如果该方法为nil,则返回nil;如果是一个表,则重复1-3;
    4. 如果是一个函数,则返回函数的返回值(table和key会作为参数传递进去)
代码语言:lua
复制
-- MetatableTest.lua
local mt = {
    __index = { c = 3, d = 4 }  -- 设置 __index 为一个包含键值对的新表
}

local t = {a=1, b=2,'a','b','c'}
setmetatable(t, mt)

-- 检查元表设置是否成功
print(getmetatable(t))  -- 应该输出: table: 地址

print(t.a)  -- 输出: 1
print(table.concat(t, '|')) -- 输出 a|b|c
print(t.c)  -- 输出: 3
print(t.d)  -- 输出: 4
print(t.e)  -- 输出: nil

2.__newindex

  • __newindex 元方法允许你自定义对表进行赋值时的行为。
  • 一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。
  • 当尝试向表中添加一个新的键或更新一个已存在的键时,Lua 会调用这个方法。这个方法可以用来拦截对表的修改操作,从而实现只读表或者其他自定义的行为。
  • 如果有必要,在调用__newindex元方法内部或者外部想绕过__newindex时可以调用 rawset 来做赋值
  • 这是因为Lua或者C/C++层面直接调用rawset设置值时是不会触发__newindex元方法的
代码语言:lua
复制
-- MetatableTest.lua
local mt = {
    __index = { c = 3, d = 4 },  -- 设置 __index 为一个包含键值对的新表
    __newindex = function(t, key, value)
        print("Setting key ", key, " to value ", value)
        rawset(t, key, value)  -- 使用 rawset 直接设置到表中
    end
}

local t = {a=1, b=2, 'a', 'b', 'c'}
setmetatable(t, mt)

-- 检查元表设置是否成功
print(getmetatable(t))  -- 应该输出: table: 地址

print(table.concat(t, '|'))  -- 输出: a|b|c
print(t.c)  -- 应该输出: 3
print(t.d)  -- 应该输出: 4
print(t.e)  -- 应该输出: nil

-- 使用 __newindex
t.newkey = "newvalue"  -- 触发 __newindex

-- 使用 rawset
rawset(t, 'anotherkey', "anothervalue")  -- 不触发 __newindex

-- 显示所有键值对
for k, v in pairs(t) do
    print(k..'|'..v)
end

newindex只读表的实现

代码语言:lua
复制
local readonl1yTable = setmetatable({}, {
    __newindex = function(tbl, key, value)
        error("Attempt to modify a read-only table")
    end
})

-- 尝试修改只读表
readonlyTable.someKey = "someValue"  -- 这里会抛出错误

在这个例子中,当我们尝试向 readonlyTable 添加一个新键或更新一个已存在的键时,Lua 会调用元表中的 __newindex 方法。由于我们定义了这个方法来抛出一个错误,因此任何对 readonlyTable 的修改都会失败,并抛出一个错误信息。

3.运算符类元方法

元表中有一些类似于CPP重载运算符的操作

当调用相应的运算符时会根据对应模式域触发相应的事件

模式

描述

add

对应的运算符 '+'

sub

对应的运算符 '-'

mul

对应的运算符 '*'

div

对应的运算符 '/'

mod

对应的运算符 '%'

unm

对应的运算符 '-'

concat

对应的运算符 '..'

eq

对应的运算符 '=='

lt

对应的运算符 '<'

le

对应的运算符 '<='

以__add为例

代码语言:lua
复制
-- MetatableTest.lua
-- 因为table_maxn在lua5.2以后废弃了所以需要自己实现
-- 自定义计算表中最大键值函数 table_maxn,即返回表最大键值
function table_maxn(t)
    local mn = 0
    for k, _ in pairs(t) do
        if type(k) == "number" and k > mn then
            mn = k
        end
    end
    return mn
end

-- __add 两表元素相加操作
print("--------------------------------------------------")
local mytable1 = setmetatable({1, 2, 3}, {
  __add = function(mytable, newtable)
    local max_key_mytable = table_maxn(mytable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, max_key_mytable + i, newtable[i])
    end
    return mytable
  end
})

local secondtable1 = {4, 5, 6}
mytable1 = mytable1 + secondtable1

print("After adding tables:")
for k, v in ipairs(mytable1) do
    print(k.."|"..v)
end

-- __add 表里的两个数值相加操作
print("--------------------------------------------------")
local mytable2 = setmetatable({1, 2, 3}, {
  __add = function(mytable, newtable)
    local max_len = math.min(#mytable, #newtable)
    for i = 1, max_len do
      mytable[i] = mytable[i] + newtable[i]
    end
    return mytable
  end
})

local secondtable2 = {4, 5, 6}
mytable2 = mytable2 + secondtable2

print("After adding values:")
for k, v in ipairs(mytable2) do
    print(k.."|"..v)
end

4.__tostring

__tostring 元方法用于控制如何将一个对象转换为字符串。当将一个对象转换成字符串时(例如,使用 tostring 函数或在 print 函数中打印一个对象),如果对象的元表中定义了 __tostring 元方法,那么这个元方法将被调用。

代码语言:lua
复制
-- MetatableTest.lua

-- __tostring 函数
print("--------------------------------------------------")
mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "表所有元素的和为 " .. sum
  end
})
print(mytable) -- 输出表所有元素和为 60

5.__call

__call 元方法用于控制如何将一个对象当作函数来调用。当你尝试将一个对象当作函数调用时(例如,使用 obj(arg1, arg2) 的形式),如果对象的元表中定义了\ __call 元方法,那么这个元方法将被调用。调用这个元方法时, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。

代码语言:lua
复制
-- MetatableTest.lua

-- 自定义计算表中最大键值函数 table_maxn,即返回表最大键值
function table_maxn(t)
    local mn = 0
    for k, _ in pairs(t) do
        if type(k) == "number" and k > mn then
            mn = k
        end
    end
    return mn
end

-- 定义元方法 __call
local mytable = setmetatable({10}, {
  __call = function(mytable, newtable)
        local sum = 0
        for i = 1, table_maxn(mytable) do
            sum = sum + mytable[i]
        end
        for i = 1, table_maxn(newtable) do
            sum = sum + newtable[i]
        end
        return "两个表所有元素和为"..sum
  end
})

local newtable = {10, 20, 30}
print(mytable(newtable)) --输出 "两个表所有元素和为70"

总结

元表定义了值在某些特定操作下的行为,根据行为域执行特定的元方法。

元表和元方法是Lua语言中强大的工具,能够帮助开发者实现更复杂的功能,并且提高代码的灵活性和可维护性。理解并正确使用元表可以使Lua程序更加健壮和高效。然而,过度使用或不恰当的使用元表可能会导致难以调试的问题,因此使用时需谨慎。


参考文章:

Lua 5.3 参考手册

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 元表简介
  • 元方法
    • 1.__index
      • 1.函数调用
      • 2.表调用
    • 2.__newindex
      • newindex只读表的实现
    • 3.运算符类元方法
      • 4.__tostring
        • 5.__call
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档