Lua中的metatable详解
转自:http://www.jb51.net/article/56690.htm
Lua 中 metatable 是一个普通的 table,但其主要有以下几个功能:
1.定义算术操作符和关系操作符的行为
2.为 Lua 函数库提供支持
3.控制对 table 的访问
Metatables 定义操作符行为
Metatable 能够被用于定义算术操作符和关系操作符的行为。例如:Lua 尝试对两个 table 进行加操作时,它会按顺序检查这两个 table 中是否有一个存在 metatable 并且这个 metatable 是否存在 __add 域,如果 Lua 检查到了这个 __add 域,那么会调用它,这个域被叫做 metamethod。
Lua 中每个 value 都可以有一个 metatable(在 Lua 5.0 只有 table 和 userdata 能够存在 metatable)。每个 table 和 userdata value 都有一个属于自己的 metatable,而其他每种类型的所有 value 共享一个属于本类型的 metatable。在 Lua 代码中,通过调用 setmetatable 来设置且只能设置 table 的 metatable,在 C/C++ 中调用 Lua C API 则可以设置所有 value 的 metatable。默认的情况下,string 类型有自己的 metatable,而其他类型则没有:
print(getmetatable('hi')) --> table: 003C86B8
print(getmetatable()) --> nil
Metamethod 的参数为操作数(operands),例如:
local mt = {}
function mt.__add(a, b)
return 'table + ' .. b
end
local t = {}
setmetatable(t, mt)
print(t + )
每个算术操作符有对应的 metamethod:
+ | __add |
* | __mul |
- | __sub |
/ | __div |
- | __unm (for negation) |
% | __mod |
^ | __pow |
对于连接操作符有对应的 metamethod:__concat
同样,对于关系操作符也都有对应的 metamethod:
== | __eq |
< | __lt |
<= | __le |
其他的关系操作符都是用上面三种表示:
a ~= b 表示为 not (a == b)
a > b 表示为 b < a
a >= b 表示为 b <= a
和算术运算符不同的是,关系运算符用于比较拥有不同的 metamethod(而非 metatable)的两个 value 时会产生错误,例外是比较运算符,拥有不同的 metamethod 的两个 value 比较的结果是 false。
不过要注意的是,在整数类型的比较中 a <= b 可以被转换为 not (b < a),但是如果某类型的所有元素并未适当排序,此条件则不一定成立。例如:浮点数中 NaN(Not a Number)表示一个未定义的值,NaN <= x 总是为 false 并且 x < NaN 也总为 false。
为 Lua 函数库提供支持
Lua 库可以定义和使用的 metamethod 来完成一些特定的操作,一个典型的例子是 Lua Base 库中 tostring 函数(print 函数会调用此函数进行输出)会检查并调用 __tostring metamethod:
local mt = {}
mt.__tostring = function(t)
return '{' .. table.concat(t, ', ') .. '}'
end local t = {, , }
print(t)
setmetatable(t, mt)
print(t)
另外一个例子是 setmetatable 和 getmetatable 函数,它们定义和使用了 __metatable 域。如果你希望设定的 value 的 metatable 不被修改,那么可以在 value 的 metatable 中设置 __metatable 域,getmetatable 将返回此域,而 setmetatable 则会产生一个错误:
mt.__metatable = "not your business"
local t = {}
setmetatable(t, mt)
print(getmetatable(t)) --> not your business
setmetatable(t, {})
stdin:: cannot change protected metatable
看一个完整的例子:
Set = {} local mt = {} function Set.new(l)
local set = {}
-- 为 Set 设置 metatable
setmetatable(set, mt)
for _, v in ipairs(l) do set[v] = true end
return set
end function Set.union(a, b)
-- 检查 a b 是否都是 Set
if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
-- error 的第二个参数为 level
-- level 指定了如何获取错误的位置
-- level 值为 1 表示错误的位置为 error 函数被调用的位置
-- level 值为 2 表示错误的位置为调用 error 的函数被调用的地方
error("attempt to 'add' a set with a not-set value", )
end
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end function Set.intersection(a, b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end mt.__add = Set.union
mt.__mul = Set.intersection mt.__tostring = function(s)
local l = {}
for e in pairs(s) do
l[#l + ] = e
end
return '{' .. table.concat(l, ', ') .. '}'
end mt.__le = function(a, b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end mt.__lt = function(a, b)
return a <= b and not (b <= a)
end mt.__eq = function(a, b)
return a <= b and b <= a
end local s1 = Set.new({, , })
local s2 = Set.new({, , })
print(s1 + s2)
print(s1 ~= s2)
控制 table 的访问
__index metamethod
在我们访问 table 的不存在的域时,Lua 会尝试调用 __index metamethod。__index metamethod 接受两个参数 table 和 key:
local mt = {}
mt.__index = function(table, key)
print('table -- ' .. tostring(table))
print('key -- ' .. key)
end local t = {}
setmetatable(t, mt)
local v = t.a
__index 域也可以是一个 table,那么 Lua 会尝试在 __index table 中访问对应的域:
local mt = {}
mt.__index = {
a = 'Hello World'
} local t = {}
setmetatable(t, mt)
print(t.a) --> Hello World
我们通过 __index 可以容易的实现单继承(类似于 JavaScrpit 通过 prototype 实现单继承),如果 __index 是一个函数,则可以实现更加复杂的功能:多重继承、caching 等。我们可以通过 rawget(t, i) 来访问 table t 的域 i,而不会访问 __index metamethod,注意的是,不要太指望通过 rawget 来提高对 table 的访问速度(Lua 中函数的调用开销远远大于对表的访问的开销)。
__newindex metamethod
如果对 table 的一个不存在的域赋值时,Lua 将检查 __newindex metamethod:
1.如果 __newindex 为函数,Lua 将调用函数而不是进行赋值
2.如果 __newindex 为一个 table,Lua 将对此 table 进行赋值
如果 __newindex 为一个函数,它可以接受三个参数 table key value。如果希望忽略 __newindex 方法对 table 的域进行赋值,可以调用 rawset(t, k, v)
结合 __index 和 __newindex 可以实现很多功能,例如:
1.OOP
2.Read-only table
3.Tables with default values
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(t, k, v)
error('attempt to update a read-only table', )
end
}
setmetatable(proxy, mt)
return proxy
end days = readOnly{'Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'}
print(days[])
days[] = 'Noday' --> stdin:1: attempt to update a read-only table
有时候,我们需要为 table 设定一个唯一的 key,那么可以使用这样的技巧:
local key = {} -- unique key
local t = {}
t[key] = value
Lua中的metatable详解的更多相关文章
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- AngularJS select中ngOptions用法详解
AngularJS select中ngOptions用法详解 一.用法 ngOption针对不同类型的数据源有不同的用法,主要体现在数组和对象上. 数组: label for value in a ...
- 【转载】C/C++中extern关键字详解
1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...
- oracle中imp命令详解 .
转自http://www.cnblogs.com/songdavid/articles/2435439.html oracle中imp命令详解 Oracle的导入实用程序(Import utility ...
- Android中Service(服务)详解
http://blog.csdn.net/ryantang03/article/details/7770939 Android中Service(服务)详解 标签: serviceandroidappl ...
- python中threading模块详解(一)
python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...
- Android中mesure过程详解
我们在编写layout的xml文件时会碰到layout_width和layout_height两个属性,对于这两个属性我们有三种选择:赋值成具体的数值,match_parent或者wrap_conte ...
- Android中Intent组件详解
Intent是不同组件之间相互通讯的纽带,封装了不同组件之间通讯的条件.Intent本身是定义为一个类别(Class),一个Intent对象表达一个目的(Goal)或期望(Expectation),叙 ...
随机推荐
- L210 Ebola
Progress in fighting Democratic Republic of the Congo's Ebola outbreak, the second worst ever, will ...
- Android RIL结构分析与移植
介绍 本文档对Android RIL部分的内容进行了介绍,其重点放在了Android RIL的原生代码部分. 包括四个主题: 1.Android RIL框架介绍 2.Android RIL与 Wind ...
- Android输入法框架系统(上)
输入法,就是用来输入字符(包括英文,俄文,中文)的工具.输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符.在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输 ...
- 5.1 socket编程、简单并发服务器
什么是socket? socket可以看成是用户进程与内核网络协议栈的编程接口.是一套api函数. socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机间的进程间通信. 工业上使用的为t ...
- CTF-练习平台-Misc之 细心的大象
十五.细心的大象 打开图片属性 发现备注里有短信息,看着也不像flag,仔细观察里面只有只有大小写字母和数字应该是base64编码,解密后得到:MSDS456ASD123zz 好像也不是flag,题目 ...
- 视觉惯性里程计Visual–Inertial Odometry(VIO)概述
周围很多朋友开始做vio了,之前在知乎上也和胖爷讨论过这个问题,本文主要来自于知乎的讨论. 来自https://www.zhihu.com/question/53571648/answer/13772 ...
- ubuntu 远程gui显示
图像从定向: ubuntu 显示系统xterm 重点是设置DISPLAY variable以及保证ubuntu和suse在同一网段即相互ping通,利用ssh -XY的方式重定向图像. 1.直接ss ...
- 1、ECMAScript 6 简介
ECMAScript 和 JavaScript 的关系 ES6 与 ECMAScript 2015 的关系 语法提案的批准流程 ECMAScript 的历史 部署进度 Babel 转码器 Traceu ...
- MySQL账号安全设置
======================================================================== 推荐账号安全设置 在数据库服务器上严格控制操作系统的账 ...
- windows 版 nginx 运行错误的一些解决方法
1. 关于文件夹的中文的问题. 错误的截图如下: 看得到这个 failed (1113: No mapping for the Unicode character exists in the targ ...