Lua 元表、元方法

每一个类型都有一个元表。

tableuserdata 可以有各自独立的元表

其他类型的值则共享其类型所属的单一元表

表(table)是 Lua 中非常重要的基本数据类型,实现其他的一些编程技术概念的基础,如模块、包、类。通过语言提供的元表和元方法可以更改表的表现行为。

元方法

元方法 简单描述
__metatable 调用 setmetatable、getmetatable方法的时候会用到这个字段
__index 当表中的索引不存在时,查找元表中是否设置了这个元方法
__newindex 同上
__add +
__sub -
__mul *
__div /
__unm -(相反数)
__mod %
__pow ^
__concat .. 连接操作符
__eq 等于
__lt 小于
__gt 大于
__metatable 保护作用
__tostring 字符串化

总的来说,在程序中通过元方法,可以: - 更改算符运算符关系运算符作用于表时的行为 - 更改对表中的元素的访问行为,简单说是重载了,[] ,[]= 运算符

这两个特性在C++特性中看起来是运算符的重载。与C++相比,Lua本身没有面向对象的支持,但是使用元方法,可以实现其中的继承,还可以实现其他的功能、如返回具有默认值的table、跟踪table访问、只读的table

继承

从原型窗口继承所有不存在的字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Window = {}   -- 名字空间

Window.prototype = {x = 0,y = 0,width = 300,height = 400}

Window.mt = {}

function Window.new(o)
-- body
setmetatable(o, Window.mt)
return o
end


Window.mt.__index = function(tabel,key)
print(key)
return Window.prototype[key]
end

w = Window.new{10,20}
print(w.height)
``


返回具有默认值的table

当访问Lua表中不存在的一个元素的时候默认的返回的是nil,如果不想返回nil,则可以利用使用__index 元方法更改返回值。可以使用下面几种方式

方式一 : 为每一个需要默认值的表创建一个新的元表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function set_default(t,default)
--todo
local mt = {
__index = function()
return default
end
}
setmetatable(t, mt)
end

tab = {x = 1,y = 2}

print(tab.x,tab.y)
set_default(tab,0)
print(tab.x,tab.y,tab.z)

方式二 : 使用同一个元表,默认值在每个表的各自内部 当有很多表需要默认值的时候,方法一会创建大量的元表,降低效率,下面这种方法更合适。所有通过新元表解决方案都会有这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

local mt = {
__index = function(t,key)
return t.___
end
}
function set_default(t,default)
t.___ = default
setmetatable(t, mt)
end

tab = {x = 1,y = 2}

print(tab.x,tab.y)
set_default(tab,0)
print(tab.x,tab.y,tab.z)


防止索引冲突,可以改进为如下

1
2
3
4
5
6
7
8
9
10
local key = {} ---- 防止索引冲突
local mt = {
__index = function(t)
return t[key]
end
}
function set_default(t,default)
t[key] = default
setmetatable(t, mt)
end

跟踪table访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function proxy(t)
--todo

local _t = t
local _mt = {
__index = function(t,k)
print('access to element'..tostring(k))
return _t[k]
end,
__newindex = function(t,k,v)
print("update element "..tostring(k).."to"..tostring(v))
_t[k] = v
end
}

setmetatable(t, _mt)
t = {}
return t
end

t = proxy{}
t.k = 111
print(t.k)

可以改进如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local index = {} -- 防止key 冲突
local _mt = {
__index = function(t,k)
print('access to element '..tostring(k))
return t[index][k]
end,
__newindex = function(t,k,v)
print("update element "..tostring(k).." to "..tostring(v))
t[index][k] = v
end
}

function proxy(t)
--todo
local proxy = {}
proxy[index] = t
setmetatable(proxy, _mt)
-- t = proxy -- 方便 t = proxy(t),proxy(t)两种形式的调用 --- error 已经是局部变量t,和外围t不是一回事,受到名字的影响
return proxy
end

只读的table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function make_read_only_table(t) 
local read_only = {} -- 重要的一步
local mt = {
__index = t,
__newindex = function (t,k,v)
error("attemp to update a read-only table")
end
}
setmetatable(read_only, mt)
return read_only
end

a = 'test'

t = make_read_only_table{13,4,6,name='webapp',temp=a}

a = 'dd'
print(t.temp)

t.temp = 1111



方案二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30


local index = {} -- 防止key 冲突
local _mt = {
__index = function(t,k)
print('access to element '..tostring(k))
return t[index][k]
end,
__newindex = function(t,k,v)
error("attemp to update a read-only table")
end
}

function make_read_only_table(t)
local read_only = {}
read_only[index] = t
setmetatable(read_only, _mt)
return read_only
end

a = 'test'

t = make_read_only_table{13,4,6,name='webapp',temp=a}

a = 'dd'
print(t.temp)

-- t.temp = 1111



Lua 元表、元方法
http://blog.soul11201.com/2015/09/18/lua-metatable/
作者
soul11201
发布于
2015年9月18日
许可协议