Lua 元表(Metatable)
Lua 语言中的 table 可以通过访问对应的 key 来得到 value 值,但是却无法对两个 table 进行各种操作
但 Lua 提供的 元表 (Metatable) 可以改变 table 默认的行为,每个行为关联了对应的元方法。比如我们可以通过元表中定义的方法来实现两个 table 的相加操作 a+b
通过使用元表,当 Lua 试图对两个 table 进行相加操作时,先检查两者之一是否有 元表,之后检查是否有一个叫__add 的字段,若找到,则调用对应的值。
__add 等字段,其对应的值(往往是一个函数或是 table)就是 元方法
处理元表
Lua 提供了两个函数来处理元表:
-
setmetatable(table,metatable) 对指定 table 设置元表(metatable),如果元表 (metatable) 中存在 __metatable 键值,setmetatable 会失败
-
getmetatable(table) 返回对象的元表 (metatable)
以下范例演示了如何对指定的表设置元表
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mytable = {tencent="Tencent"} -- 普通表 mymetatable = {} -- 元表 setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
以上代码也可以直接写成一行:
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mytable = setmetatable({tencent="Tencent"},{})
以下为返回对象元表:
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. getmetatable(mytable) -- 这回返回mymetatable
__index 元方法
Lua 语言中的 __index 元方法是 metatable 最常用的元方法
如果 __index 是另一个 table
当通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable 中的 __index 键。如果 __index 包含一个 table ,Lua 则会在 table 中查找相应的键
范例
$ lua -- 进入 Lua 交互式命令行模式 Lua 5.3.3 Copyright (C) 1994-2016 Lua.org, PUC-Rio > mytable = { baidu = 17 } > t = setmetatable({},{ __index = mytable}) > t.baidu 17 > t.ali nil >
如果 __index 是一个函数
如果 __index 是一个函数,那么 Lua 就会调用那个函数,table 和键会作为参数传递给函数
__index 元方法查看表中元素是否存在:
- 如果不存在,返回结果为 nil
- 如果存在则由 __index 返回结果
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mytable = setmetatable({ tencent = "Tencent"}, { __index = function(mytable, key) if key == "tencent" then return "metatable value" else return "not exist" end end }) print(mytable.tencent) print(mytable.baidu)
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua Tencent not exist
范例解析:
-
mytable 表赋值为 { tencent = "Tencent"}
-
mytable 设置了元表,元方法为 __index
-
在 mytable 表中查找 tencent,如果找到,返回该元素,找不到则继续。
-
在 mytable 表中查找 baidu,如果找到,返回 metatablevalue,找不到则继续。
-
判断元表有没有 __index 方法,如果 __index 方法是一个函数,则调用该函数
-
元方法中查看是否传入 "baidu" 键的参数( mytable.baidu 没有设置),如果传入 "baidu" 参数返回 "not exist",否则返回 mytable 对应的键值。
上面的范例可以简写为:
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mytable = setmetatable({tencent = "Tencent"}, { __index = { baidu = "metatablevalue" } }) print(mytable.tencent,mytable.baidu)
运行以上 Lua 脚本,输出结果如下
$ lua main.lua Tencent metatablevalue
__index 元函数总结
Lua查找一个表元素时的规则,其实就是如下3个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
- 判断元表有没有__index方法,如果__index方法为nil,则返回nil; 如果 __index 方法是一个表,则重复1、2、3; 如果 __index 方法是一个函数,则返回该函数的返回值
__newindex 元方法
Lua 语言中的 __newindex 元方法用于对 表(table) 进行更新
当给表的一个新的的索引赋值时,解释器就会查找该表的 __newindex 元方法: 如果存在则调用这个函数而不进行赋值操作
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mymeta = {} mytable = setmetatable( {tencent="Tencent"}, { __newindex = mymetatable }) print(mytable[1]) mytable["baidu"] = "Baidu" print(mytable["baidu"],mymeta["baidu"]) mytable["ali"] = "Ali" print(mytable["ali"],mymeta["ali"])
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua nil Baidu nil Ali nil
以上范例为表设置了元方法 __newindex:
- 在对新索引键(newkey)赋值时
mytable["baidu"] = "Baidu"
,会调用元方法,而不进行赋值。 - 如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex
范例 :给 __newindex 赋值一个 rawset 函数
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. mytable = setmetatable({ tencent = "Tencent"}, { __newindex = function(mytable, key, value) rawset(mytable, key, "\""..value.."\"") end }) mytable.ali = "Ali" mytable.twle = 28 print(mytable.ali,mytable.twle)
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua "Ali" "28"
为表(table) 添加操作符
重写 __add() 方法可以实现两个 table 相加
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. -- 该函数和 Lua5.2 中的 table.maxn 类似 -- table.maxn 在Lua5.2以上版本中已无法使用 -- 自定义计算表中最大值函数 maxn_of function maxn_of(t) local mn = 0 for k, v in pairs(t) do if mn < k then mn = k end end return mn end -- 两表相加操作 mytable = setmetatable({ 1, 3, 5 }, { __add = function(mytable, newtable) for i = 1, maxn_of(newtable) do table.insert(mytable, maxn_of(mytable)+1,newtable[i]) end return mytable end }) secondtable = {7,11,13} mytable = mytable + secondtable for k,v in ipairs(mytable) do print(k,v) end
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua 1 1 2 3 3 5 4 7 5 11 6 13
下表列出了元表可用的操作符
模式 | 描述 |
---|---|
__add | 对应的运算符 '+' |
__sub | 对应的运算符 '-' |
__mul | 对应的运算符 '*' |
__div | 对应的运算符 '/' |
__mod | 对应的运算符 '%' |
__unm | 对应的运算符 '-' |
__concat | 对应的运算符 '..' |
__eq | 对应的运算符 '==' |
__lt | 对应的运算符 '<' |
__le | 对应的运算符 '<=' |
__call 元方法
Lua 语言中的 __call 元方法用于直接调用 table 变量名时时候
__call 元方法语法格式如下
mixed __call(table,newtable)
范例 : 使用 __call 计算 table 的总和
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. -- 该函数和 Lua5.2 中的 table.maxn 类似 -- table.maxn 在Lua5.2以上版本中已无法使用 -- 自定义计算表中最大值函数 maxn_of function maxn_of(t) local mn = 0 for k, v in pairs(t) do if mn < k then mn = k end end return mn end -- 定义元方法__call mytable = setmetatable({17}, { __call = function(mytable, newtable) sum = 0 for i = 1, maxn_of(mytable) do sum = sum + mytable[i] end for i = 1, maxn_of(newtable) do sum = sum + newtable[i] end return sum end }) newtable = {1,3,5} print(mytable(newtable))
运行以上 Lua 脚本,输出结果如下:
$ lua main.lua
26
__tostring 元方法
__tostring 元方法用于修改表的输出行为
__tostring 元方法语法格式如下
string __tostring()
范例 : 使用 __tostring() 方法自定义 table
的输出
-- !/usr/bin/lua -- -*- encoding:utf-8 -*- -- filename: main.lua -- author: 简单教程(www.twle.cn) -- Copyright © 2015-2065 www.twle.cn. All rights reserved. tbl1 = {"baidu","tencent","ali","twle"} mytable = setmetatable(tbl1, { __tostring = function(mytable) str1 = "" sep = "" for k, v in pairs(mytable) do str1 = str1 .. sep .. k .. ":" .. v if k == 1 then sep = "," end end return str1 end }) print(mytable)
运行以上 Lua 脚本,输出结果如下
$ lua main.lua
1:baidu,2:tencent,3:ali,4:twle