【Lua 入门基础篇(八)】元表
文章目录
- 一、元表(Metatable)
- 1. setmetatable
- 2. __tostring 元方法
- 3. __call 元方法
- 4. __index 元方法
- 5. __newindex 元方法
- 6. 运算符重载
一、元表(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
可以嵌套。
metafather = {
age = 3,
__index = { age = 2 }
}
meta = { __index = metafather }
-- 去到指定表找,而不是继续找metafather的__index对应的表
table = {}
setmetatable(table, meta)
setmetatable(meta, metafather)
print(table.age) --输出:3
-- 如果注释调第二行age=3,则输出nil
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)