15.元表与元方法
元表,即 Lua 中普通 table 的元数据表,而元方法则是元表中定义的普通表的默认行为。
Lua 中的每个普通 table 都可为其定义一个元表,用于扩展该普通 table 的行为功能。例如,
对于 table 与数值相加的行为,Lua 中是没有定义的,但用户可通过为其指定元表来扩展这
种行为;再如,用户访问不存在的 table 元素,Lua 默认返回的是 nil,但用户可能并不知道
发生了什么。此时可以通过为该 table 指定元表来扩展该行为:给用户提示信息,并返回用
户指定的值。
(1) 重要函数
元表中有两个重要函数:
setmetatable(table,metatable):将 metatable 指定为普通表 table 的元表。
getmetatable(table):获取指定普通表 table 的元表。
(2) _ index 元方法
当用户在对 table 进行读取访问时,如果访问的数组索引或 key 不存在,那么系统就会
自动调用元表的 index 元方法。该重写的方法可以是一个函数,也可以是另一个表。如果
重写的 _index 元方法是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。
__index元方法是一个函数如下:
--元表与元方法
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
--定义一个元表
meta = {}
--关联原始表和元表
setmetatable(t1,meta)
function meta.__index(tab,key)
print("key "..key.." 在原始表中不存在")
end
print(t1[99])
print(t1[100])
//输出结果如下:
key 99 在原始表中不存在
nil
key 100 在原始表中不存在
nil
__index元方法是另一个表如下
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
--定义一个元表
meta = {}
--定义一个元表
meta = {}
--定义另一个普通表
other = {}
other[99] = 999
other[100] = 1000
--关联原始表和元表
setmetatable(t1,meta)
meta.__index = other
print(t1[99])
print(t1[100])
//输出结果如下:
999
1000
3.当用户为 table 中一个不存在的索引或 key 赋值时,就会自动调用元表的_ newindex 元
方法。该重写的方法可以是一个函数,也可以是另一个表。如果重写的 _newindex 元方法
是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。
__newindex是函数,如下:
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
--元表
meta = {};
setmetatable(t1,meta)
meta.__newindex = function(tab,key,value)
print("写 "..key.." value不存在")
--新值写入原始表
rawset(tab,key,value)
end
t1[100] = 1900
t1.x = "xxx"
print (t1[100])
print (t1.x)
//输出结果如下:
写 100 value不存在
写 x value不存在
1900
xxx
__newindex是另一个表,如下:
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
meat = {}
other = {}
setmetatable(t1,meat)
meat.__newindex = other --此时,other的作用是,暂存新增加的数据
t1.x = "xxx369"
print(t1.x)
print(other.x)
//输出结果如下:
nil
xxx369
4.运算符元方法
如果要为一个表扩展加号(+)、减号(-)、等于(==)、小于(<)等运算功能,则可重写相应的
元方法。
例如,如果要为一个 table 扩展加号(+)运算功能,则可重写该 table 元表的_ add 元方
法,而具体的运算规则,则是定义在该重写的元方法中的。这样,当一个 table 在进行加法
(+)运算时,就会自动调用其元表的 _add 元方法。
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
meta = {
__add = function(tab,num)
for k,v in pairs(tab) do
if (type(v) == "number") then
tab[k] = v + num
elseif (type(v) == "string" and type(k) ~= "string") then
tab[k] = tab[k]..".xxx"
end
end
return tab
end
}
setmetatable(t1,meta)
t2 = t1 + 3
for k,v in pairs(t2) do
print(k,v)
end
//输出结果如下:
1 a.xxx
2 b.xxx
3 c.xxx
4 d.xxx
5 103
age 26
name 王五
5._tostring元方法
直接输出一个 table,其输出的内容为类型与 table 的存放地址。如果想让其输出 table
中的内容,可重写 _tostring 元方法。
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
meta = {}
setmetatable(t1,meta)
meta.__tostring = function (tab)
str = ""
for k, v in pairs(tab) do
str = str..k..": "..v.."\n"
end
return str
end
print(t1)
//输出结果如下:
1: a
2: b
3: c
4: d
5: 100
age: 23
name: 王五
6._call元方法
当将一个 table 以函数形式来使用时,系统会自动调用重写的 _call 元方法。该用法主
要是可以简化对 table 的相关操作,将对 table 的操作与函数直接相结合。
t1 = {"a", age = 23,"b","c",name = "王五", "d",100}
meta = {}
setmetatable(t1,meta)
function meta.__call(tab,num,str)
for k,v in pairs(tab) do
if (type(v) == "string") then
tab[k] = tab[k]..str
else
tab[k] = v + num
end
end
return tab
end
t1(1,"xx")
for k,v in pairs(t1) do
print(k, v)
end
//输出结果如下:
1 axx
2 bxx
3 cxx
4 dxx
5 101
age 24
name 王五xx
7.元表单独定义
为了便于管理与复用,可以将元素单独定义为一个文件。该文件中仅可定义一个元表,
且一般文件名与元表名称相同。
若一个文件要使用其它文件中定义的元表,只需使用 require “元表文件名”即可将元表
导入使用。
如果用户想扩展该元表而又不想修改元表文件,则可在用户自己文件中重写其相应功能
的元方法即可。