如何简单易懂的理解lua的元表metatable
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何简单易懂的理解lua的元表metatable相关的知识,希望对你有一定的参考价值。
参考技术A Lua 的 metatable 并不只是为了 OO 而存在,但 Lua 的面向对象机理,prototype based object orientation,的确是通过 metatable 而实现的。简单来说,prototype based OO 的根本特征是不采用「类」这一层抽象,也就是说,所有对象都是没有 class 的 instance。当一个 object 需要某个其他 object 已有的功能时,可以将后者设为自己的 prototype;同一个 object 可以同时作为几个其他 object 共同的 prototype(但反之通常不成立);已指定 prototype 的 object 还可以成为其他 object 的 prototype;一个 object 可以以自己为 prototype。一系列 object 以此方式组成一串 prototype 链,在某个 object 上调用它并未定义的方法或属性时,系统会上溯这条链,依次查看每一级 prototype 是否有定义,直至找到或穷尽到链条终点为止。举例而言,假设有三个维京人,甲会弹弓,乙会用剑,丙会持盾牌;甲给乙当 prototype,于是乙也会了弹弓;乙再给丙当 prototype,丙就成了可以持盾挥剑射弹弓的全能维京人。甲没有 prototype(或者有一个什么都不会的缺省 prototype),丙则不是任何人的 prototype(但他可以是)。
具体到 Lua 这里,这种「向上寻找 prototype」的机制,可以用 metatable 之中的 metamethod 完成。最简单但并不是最简洁的例子:
a = foo = function() return 'foo' end
b = bar = function() return 'bar' end
c = __index = b
setmetatable(a, c)
a.bar()
-- 'bar'
将 table c 的 __index 定义为 table b,然后将 a 的 metatable 定为 c,就让 b 变成了 a 的 prototype。而 c 作为 metatable 的角色其实可以融合到 b 或者 a 中:
a = foo = function() return 'foo' end
b = bar = function() return 'bar' end
b.__index = b
setmetatable(a, b)
a.bar()
-- 'bar'
这样一来,b 既是 a 的 prototype,也是 a 的 metatable。将 __index 指向另一个 table 大概是它最简单也最普遍的用法,只是 Lua 官方教程中引入 __index 的部分写得比较复杂,把它写成了一个函数,意图大概是考虑到大部分读者对 prototype based OO 并不是很了解(那时候 javascript 还没有现在这么流行),所以用 metatable 来强行实现了一个看起来很接近 class-based 的 OO。 参考技术B 2、老罗拉了一车梨, 参考技术C 八、让中、英文输入法智能化地出现
Lua 的元表
简述
元表指的是 Lua 中的 MetaTable
,它提供了一种重定义任意一个 Lua 中对象或值默认行为的公开入口,让 Lua 也能像许多面向对象语言一样实现操作符或方法的重载。
原理解析
在 Lua 中,每种类型的数据都有其默认的操作,如:number 有加减操作,string 的拼接操作,function 的调用操作,这些都是该类型的默认操作,而我们可以通过修改其原来来实现对这些默认操作进行修改。
元表(MetaTable)和元方法
用来定义对 table 或 userdata 操作方式的表,例如通过元表来定义 table 相加 "+"
操作(类似与 C 语言中的运算符重载)
local mt =
-- 合并两个表
mt.__add = function(t1, t2)
local temp =
for _,v in pairs(t1) do
table.insert(temp,v)
end
for _,v in pairs(t2) do
table.insert(temp,v)
end
return temp
end
local tb1 = 1, 2
local tb2 = 3
-- 为 tb1 设置元表为 mt
setmetatable(tb1, mt)
local tb3 = tb1 + tb2
如上,当 tb1 + tb2 时,会调用 tb1 的元表中的 __add 元方法来计算结果。具体步骤如下:
-
检查 tb1 是否有元表,若有,则查看其元表中是否有 __add 元方法,若有则调用;
-
检查 tb2 是否有元表,若有,则查看其元表中是否有 __add 元方法,若有则调用;
-
若都没有,则会报错。
因此只需要 tb1 或 tb2 其中一个有元表且具有 __add 元方法即可。
元方法: 上述 __add
就叫做 元方法
,常见的元方法还有:
函数 | 描述 |
---|---|
__add | 运算符 + |
__sub | 运算符 - |
__mul | 运算符 * |
__ div | 运算符 / |
__mod | 运算符 % |
__unm | 运算符 -(取反) |
__concat | 运算符 .. |
__eq | 运算符 == |
__lt | 运算符 < |
__le | 运算符 <= |
__call | 当函数调用 |
__tostring | 转化为字符串 |
__index | 调用一个索引 |
__newindex | 给一个索引赋值 |
1. __index
元方法
-
作为函数时:将表与索引传入 __index 元方法,并 return 一个返回值
local mt = --第一个参数是表自己,第二个参数是调用的索引 mt.__index = function(t,key) return "it is "..key end t = 1,2,3 --输出未定义的key索引,输出为nil print(t.key) setmetatable(t,mt) --设置元表后输出未定义的key索引,调用元表的__index函数,返回"it is key"输出 print(t.key)
结果:
nil it is key
-
作为 table 时:查找 __index 元方法表,若有该索引,则返回该索引对应的值,否则返回 nil
local mt = mt.__index = key = "it is key" t = 1,2,3 --输出未定义的key索引,输出为nil print(t.key) setmetatable(t,mt) --输出表中未定义,但元表的__index中定义的key索引时,输出__index中的key索引值"it is key" print(t.key) --输出表中未定义,但元表的__index中也未定义的值时,输出为nil print(t.key2)
结果:
nil it is key nil
2. __newindex 元方法
当为 table 中一个不存在的索引赋值时,会调用元表中的 _newindex
元方法
-
作为函数时:会将赋值语句中的表、索引、赋的值当做参数去掉用(不对表进行改变)
local mt = --第一个参数时表自己,第二个参数是索引,第三个参数是赋的值 mt.__newindex = function(t,index,value) print("index is "..index) print("value is "..value) end t = key = "it is key" setmetatable(t,mt) --输出表中已有索引key的值 print(t.key) --为表中不存在的newKey索引赋值,调用了元表的__newIndex元方法,输出了参数信息 t.newKey = 10 --表中的newKey索引值还是空,上面看着是一个赋值操作,其实只是调用了__newIndex元方法,并没有对t中的元素进行改动 print(t.newKey)
结果:
it is key index is newKey value is 10 nil
-
作为 table 时:为原 table 中不存在的索引赋值会将该索引和值赋到 __newindex 所指向的表中,不对原 table 进行改变
local mt = --将__newindex元方法设置为一个空表newTable local newTable = mt.__newindex = newTable t = setmetatable(t,mt) print(t.newKey,newTable.newKey) --对t中不存在的索引进行负值时,由于t的元表中的__newindex元方法指向了一个表,所以并没有对t中的索引进行赋值操作将,而是将__newindex所指向的newTable的newKey索引赋值为了"it is newKey" t.newKey = "it is newKey" print(t.newKey,newTable.newKey)
结果:
nil nil nil it is newKey
3. rawget 和 rawset
-
rawget 可以直接获取表中索引的真实值,而不通过 __index 元方法
local mt = mt.__index = key = "it is key" t = setmetatable(t,mt) print(t.key) --通过rawget直接获取t中的key索引 print(rawget(t,"key"))
结果:
it is key nil
-
rawset 可以直接为表中索引赋值,而不通过 __newindex 元方法
local mt = local newTable = mt.__newindex = newTable t = setmetatable(t,mt) print(t.newKey,newTable.newKey) --通过rawset直接向t的newKey索引赋值 rawset(t,"newKey","it is newKey") print(t.newKey,newTable.newKey)
结果:
nil nil it is newKey nil
运用场景
-
通过为 table 设置元表,可以在 lua 中实现面向对象编程
-
通过对 userdata 和元表,可以实现 lua 中对 c 中的结构进行面向对象式的访问
参考:
以上是关于如何简单易懂的理解lua的元表metatable的主要内容,如果未能解决你的问题,请参考以下文章