【Lua 入门基础篇(八)】元表

简介: 笔记

一、元表(Metatable)


Lua的表本质其实是个类似HashMap的东西,其元素是很多的Key-Value对,如果尝试访问了一个表中并不存在的元素时,就会触发Lua的一套查找机制,也是凭借这个机制来模拟了类似“继承”的行为。


在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。


因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。


例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。

当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add 的字段,若找到,则调用对应的值。 __add 等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。

有两个很重要的函数来处理元表:


setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。


getmetatable(table): 返回对象的元表(metatable)。

mytable = {}                      -- 普通表
mymetatable = {}                  -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表

以下为返回对象元表:

getmetatable(mytable) -- 返回 mymetatable

1. setmetatable

元表概念:


任何表变量都可以作为另一个表变量的元表

任何表变量都可以有自己的元表(父表)。

当子表中进行一些特定的操作,会执行元表中的内容。

设置 table 的元表为 meta。


第一个参数:子表

第二个参数:元表

meta = {}
table = {}
setmetatable(table, meta)

2. __tostring 元方法

当子表要被当作字符串使用,会默认调用元表中的__tostring方法。

fa = {
    __tostring = function()
        return "fa"
    end
}
son = {}
setmetatable(son, fa)
print(son) -- 执行fa表中的 __tostring
-- 输出:fa

传入参数,默认将关联的表传入

fa = {
    __tostring = function(a)
        return a.name
    end
}
son = { name = 'lua' }
setmetatable(son, fa)
print(son)  -- lua

3. __call 元方法

子表被当作一个函数来使用时,会默认调用元表中的 __call 中的内容。

参数表中的第一个参数默认为调用的子表自己。

table = { name = 'lua' }
meta = {
  __tostring = function(a)
    return a.name
  end, -- 注意!!! 这里,隔开
  __call = function(a, b)
    print(a, b)
  end
}
setmetatable(table, meta)
table(1) -- lua   1

只有在元表中实现了 __call 才能将表当作函数调用。


4. __index 元方法

通过如下代码段,解释__index方法的含义。


访问table.age时,table中没有age这个成员,但Lua接着发现table有元表meta。此时,Lua并不是直接在table中找名为age的成员,而是调用meta的__index方法。如果__index方法为nil,则返回nil;如果是一个表,那么就到__index方法所指定的这个表中查找age成员(递归查找)。


注:__index方法除了可以是一个表,还可以是一个函数,如果是一个函数,__index方法被调用时将返回该函数的返回值。

meta = {
    age = 1,
    __index = { age = 2 } -- 指定表 { age = 2 }
    -- __index = meta 不可这样写,会返回nil,可以写在外面
}
meta.__index = meta -- 指定自己
table = {}
setmetatable(table, meta)
print(table.age) -- 输出:2

__index指定函数示例:

table = {}
meta = {
    __index = function(a, b)
        if b == "name" then
            return "lua"
        else
            return nil
        end
    end
}
setmetatable(table, meta)
print(table.name)

__index 可以嵌套。

u p d a t e : update:update: 2023 20232023年1 11月6 66日16 1616点24 2424分
#!/usr/local/bin/lua
meta_father_plus_plus = { name = "meta_father_plus_plus" } 
meta_father_plus_plus.__index = meta_father_plus_plus 
meta_father_plus = {} -- 这个(plus)表没有name,去到元表(plusplus)的__index表(plusplus)中查找,-> 成功找到
meta_father = { name = "meta_father", __index = meta_father_plus }
meta = {} -- 没有name,去到元表(meta_father)的__index表(plus)中查找。
setmetatable(meta, meta_father) 
setmetatable(meta_father_plus, meta_father_plus_plus)
print(meta.name) -- meta_father_plus_plus

5. __newindex 元方法

先看一个例子:

给不存在的索引赋值,table.age=1,那么可以直接修改table表中,增加了成员age=1。

table = {}
table.age = 1
print(table.age) -- 1


当一个表有元表时,并且存在元方法__newindex。那么对子表赋值一个不存在的索引,会将这个值赋值到__newindex所指定的表中,并不会修改自己。

table = {}
meta = {}
meta.__newindex = {}
setmetatable(table, meta)
table.name = "lua"
print(table.name) -- nil
print(meta.__newindex.name) -- lua

如果只想知道自己表内有没有这个变量,忽略 __index,那么可以使用 rawget。

print(rawget(table, 'name')) -- nil
print(rawget(meta, 'name')) -- nil
print(rawget(meta.__newindex, 'name')) -- lua
  • 如果只想对自己的表进行修改,忽略 __newindex设置 ,可以对应使用 rawset
rawset(table, 'height', '180')
print(table.height) -- 180

总结:


__newindex 元方法用来对表更新,__index则用来对表访问 。

如果 __newindex 是一个函数,则给table中不存在的字段赋值时,会调用这个函数,并且赋值不成功。

如果 __newindex 是一个table,则给table中不存在的字段赋值时,会直接给 __newindex 的table赋值。


6. 运算符重载

可以重载如下这些运算符:

-- 运算符 -: __add
-- 运算符 -: __sub
-- 运算符 *: __mul
-- 运算符 /: __div
-- 运算符 %: __mod
-- 运算符 ^: __pow
-- 运算符 ..: __concat
-- 运算符 ==: __eq
-- 运算符 <: __lt
-- 运算符 <=: __le
-- __eq, __lt, __le
-- (其它运算关系自动转换为这三个的基本运算)
-- 注意:条件运算符没有 ~=, >, >=, 只能通过 not 取反, 但实际上不需要。
-- 因为 t1 > t2 相当于 t2 < t1,也是重载 <
--(这也是两个对象的元表一定要一致才能正确使用条件运算符的原因)
meta = {
    __add = function(a, b)
        return a.age + b.age
    end,
    __eq = function(a, b)
        return true
    end,
    __concat = function(a, b)
        return a.name .. ' + ' .. b.name
    end
}
a = { age = 20, name = 'A' }
b = { age = 18, name = 'B' }
setmetatable(a, meta)
print(a + b)
print(a .. b)
print(a == b)
setmetatable(b, meta)
print(a == b)
38
A + B
false
true

如果要用条件运算符来比较两个对象,这两个对象的元表一定要一致,才能准确调用方法。(b没设置元表是false,设置元表后是true)

相关文章
|
索引
lua元表、元方法
lua元表、元方法
73 0
|
3月前
|
索引
Lua语法(三)——元表与元方法
Lua语法(三)——元表与元方法
39 0
|
6月前
|
Kubernetes NoSQL Java
Lua 元表及常见元方法
Lua 元表及常见元方法
|
NoSQL 安全 Java
Redis从入门到精通之Lua 脚本
Lua 是一种轻量级的脚本语言,被广泛应用于游戏开发、嵌入式系统、Web 开发、科学计算等领域。Redis 内置了 Lua 解释器,使得用户可以通过编写 Lua 脚本来扩展 Redis 的功能。在 Redis 中,可以使用 EVAL 和 EVALSHA 命令执行 Lua 脚本。
766 7
Redis从入门到精通之Lua 脚本