Metatables允许我们改变table的行为,例如,使用Metatables我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时,他会检查两个表是否有一个表有Metatable,并且检查Metatable是否有__add域。如果找到则调用这个__add函数(所谓的Metamethod)去计算结果。
Lua中的每一个表都有其Metatable。Lua默认创建一个不带metatable的新表
如果类比C++,原表有些类似于一个关于表操作的基类+operate操作。其他的表都是继承与这个基类。可以在这个基类中定义关于表的操作。不同之处在于,C++中的继承关系需要在声明中确定,但是Lua中的这种类似的继承关系不需要声明的时候就确定,而是在声明之后再确定的。
一、自定义原表的方法
例如,《Lua in Programming》中的关于两个表运算,即求并如下:
</pre><pre name="code" class="plain">Set={};
Set.mt={};
--mt={};
function Set.new (t)
local set={};
setmetatable(set,Set.mt);
--setmetatable(set,mt);
for _,l in ipairs(t) do set[l] = true end
return set
end
--并集
function Set.union(a,b)
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
function Set.tostring(set)
local s="{";
local sep="";
for e in pairs(set) do
s=s .. sep .. e;
sep=",";
end
return s .. "}"
end
function Set.print(s)
print(Set.tostring(s));
end
Set.mt.__add=Set.union;
--mt.__add=Set.union;
Set.mt.__mul=Set.intersection;
--mt.__mul=Set.intersection;
s1=Set.new{10,20,30,50};
s2=Set.new{30,1};
print(getmetatable(s1));
print(getmetatable(s2));
s3=s1+s2;
Set.print(s3);
s4=s1*s2;
Set.print(s4)
输出:
table: 0080B488
table: 0080B488
{1,30,10,50,20}
{30}
注意:
1、用注释替换其上一行代码,结果一样
2、__add,__mul都是两个下划线
两个表关系运算,接上例:
<pre name="code" class="plain">Set.mt.__le=function(a,b)
for k in pairs(a) do
if not b[k] then
return false;
end
end
return true;
end
Set.mt.__lt=function(a,b)
return a<=b and not (b<=a)
end
Set.mt.__eq=function(a,b)
return a<=b and b>=a
end
s5=Set.new{2,4}
s6=Set.new{4,10,2}
print(s5<=s6)
print(s5<s6)
print(s5>=s5)
print(s5>s6)
print(s5==s5*s6)
输出:
true
true
true
false
true
问题:在SciTE下,有时候需要复制s5,s6在粘贴。好像是输入法影响的问题。
二、原表本身的方法
1、__index metamethod
即索引。对任何一个表通过索引取值的时候,其实都是通过__index metamethod去寻址取值的。当然可以更改索引方法的取值,类似于C++中的operate[]。代码如下:
window={};
window.protype={x=1,y=0,width=100,height=200,}
window.mt={};
function window.new(o)
setmetatable(o,window.mt)
return o;
end
window.mt.__index=function(table,key)
return window.protype[key]
end
--window.mt.__index=window.protype;
w=window.new{x=10,y=20};
print(w.x)
print(w.y)
print(w.width)
print(w.height)
结果:
10
20
100200
<span style="font-family: KaiTi_GB2312; background-color: rgb(255, 255, 255);">实质上就是重写了[]的作用。用指数替换上一个函数结果相同。</span>
2、为边添加默认值:使用索引值获取表的内容且索引值不存在的时候,返回nil。如果想要返回特定值,方法如下:
function setdefault(t,d)
local mt={__index=function() return d;end}
setmetatable(t,mt);
end
t={x=10,y=20}
print(t.x,t.y,t.z);
setdefault(t,0);
print(t.x,t.y,t.z);
结果:
10 20 nil
10 20 0
3、监控表:就是监控表的__index和__newindex两个操作
t={};
local _t=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 of element"..tostring(k).."to"..tostring(v));
_t[k]=v;
end;
}
setmetatable(t,mt);
t[2]="hello"
print(t[2])
结果:
update of element2tohello
access to element2
hello
简而言之就是监督所有的所以的索引操作,通过索引取值和新建一个索引。
3、只读表:通过以上两个例子可以容易想到,所谓的只读表就是将__index和__newindex的功能改变:
function readonly(t)
local proxy={};
local mt={
__index=t;
__newindex=function(t,k,v)
print("error",2)
end
}
setmetatable(proxy,mt);
return proxy;
end
data=readonly{"1","2","3"};
print(data[1]);
data[2]=3;
结果:
1
error 2
与C++类比,有点像一个对象只能在栈上生成的时候,就是将new设为私有。
Lua有很多可以和C++类比的地方,可以通过类比理解。