一、元表(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)