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++类比的地方,可以通过类比理解。