# lua流水账3：Metatables and Metamethods

Metatables允许我们改变table的行为，任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)

1.getmetatable(t):获取t的metatable,setmetatable(t,tmetatable):设置t的metatable为tmetatable

```t = {};
print(getmetatable(t));
t1 = {};
setmetatable(t,t1);
if getmetatable(t) == t1 then
print("t's metatable is t1");
end```

3.Metatables也允许我们使用metamethods:__eq（等于），__lt（小于），__le（小于等于）给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,因为Lua将a ~= b转换为not(a==b);a>b转换为b

```Set = {};
Set.mt = {};

function Set.new(t)
local set = {};
setmetatable(set,Set.mt);
for _,l in ipairs(t) do
set[l] = true
end
return set;
end
--加
function Set.union(a,b)
if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then
error("attempt to 'add' a set with a non-set value",2);
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

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
--当Lua试图对两个集合相加时，将调用这个函数，以两个相加的表作为参数
--乘
Set.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);
Set.print((s1 + s2) * s1);
--s = Set.new{1,2,3}
--s = s + 8

--小于等于
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

s1 = Set.new{2,4};
s2 = Set.new{4,10,2};
print(s1 <= s2);
print(s1 < s2);
print(s1 >= s1);
print(s1 > s1);
print(s1 == s2 * s1);

--tostring
Set.mt.__tostring = Set.tostring;
s1 = Set.new{10,4,5};
print(s1);

--保护metatable不被setmetatable修改
s1 = Set.new{};
print(getmetatable(s1));
--报错输出：cannot change a protected metatable
--setmetatable(s1,{});```

6.__index：当我们访问一个表的不存在的域，返回结果为nil,这是正确的，但并不一致正确。实际上，这种访问触发lua解释器去查找__index metamethod：如果不存在，返回结果为nil;如果存在则由__index metamethod返回结果。

```Window = {};
Window.prototype = {x = 0,y = 0,width = 100,height = 100,}
Window.mt = {};
function Window.new(o)
setmetatable(o,Window.mt);
return o;
end

Window.mt.__index = function(table,key)
return Window.prototype[key];
end
--上述函数可以写为：Window.mt.__index = Window.prototype

w = Window.new{x = 10,y = 20};
--先找w中的width,找不到，找w的metatable即Window.mt的__Index
print(w.width);```

7.__newindex metamethod用来对表进行更新,__index则用来对表访问。当你给表的一个缺少的域赋值，解释器就会查找__newindex metamethod：如果存在则调用这个函数而不进行赋值操作。像__index一样，如果metamethod是一个表，解释器对指定的那个表，而不是原始的表进行赋值操作。另外，有一个raw函数可以绕过metamethod：调用rawset(t,k,v)不调用任何metamethod对表t的k域赋值为v。__index和__newindex metamethods的混合使用提供了强大的结构：从只读表到面向对象编程的带有继承默认值的表。

```function setDefault(t,d)
local mt = {__index = function() return d end};
setmetatable(t,mt);
end

tab = {x = 10,y = 20};
print(tab.x,tab.z);
setDefault(tab,0);
print(tab.x,tab.z);

10  0```
```t = {};
local _t = t;
t = {};
local mt = {
__index = function(t,k)
return _t[k];             --access the original table
end,
__newindex = function(t,k,v)
print("*update to element "..tostring(k).." to "..tostring(v))
_t[k] = v;                --update original table
end,
}
setmetatable(t,mt);

t[2] = 'hello';
print(t[2])

hello```
```--不知道这个的应用场景
--create private index
local index = {};
local mt = {
__index = function(t,k)
return t[index][k];
end
__newindex = function(tk,k,v)
pritn("*update of element "..tostring(k).." to "..tostring(v));
t[index][k] = v;
end
}

function track(t)
local proxy = {};
proxy[index] = t;
setmetatable(proxy,mt);
return proxy;
end```
```--只读表
local proxy = {};
local mt ={
__index = t;
__newindex = function(t,k,v)
error("attempt to update a read-only table",2)
end
}
setmetatable(proxy,mt);
return proxy;
end

print(days[1]);
days[2] = "Noday";

lua: testLua:16: attempt to update a read-only table
stack traceback:...```

8.__mode制定表的weak性。在这个域存在的时候，必须是个字符串：如果这个字符串包含小写字母 ‘k’ ,这个table中的keys就是weak的；如果这个字符串包含小写字母 ‘v’,这个table中的values就是weak的。
Lua自动进行内存的管理。程序只能创建对象（表，函数等），而没有执行删除对象的函数。通过使用垃圾收集技术，Lua会自动删除那些失效的对象。垃圾回收器只能在确认对象失效之后才会进行收集；它是不会知道你对垃圾的定义的。

Weak表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。一个weak引用是指一个不被Lua认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak，对象将被收集，而那些weak引用将会被删除。Lua通过weak table来实现weak引用：一个weak tables是指所有引用都是weak的table。这意味着，如果一个对象只存在于weak tables中，Lua将会最终将它收集。

```a = {};
b = {};
setmetatable(a,b);
b.__mode = "k";
key = {};
a[key] = 1;
--覆盖了第一个key的值。当垃圾收集器工作时，在其他地方没有指向第一个key的引用
--所以它被收集了，因此相对应的table中的入口也同时被移除了。但是第二个key仍然是占用
--活动的变量key,所以它不会被收集。
key = {};
a[key] = 2;

collectgarbage();
for k,v in pairs(a) do
print(v);
end```
```--记忆函数
local results = {};
setmetatable(result,{__mode = "v"};