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

2.对于每一个算术运算符,metatable都有对应的域名与其对应,如__add(加),__mul(乘),__sub(减),__div(除),__unm(负),__pow(幂),我们也可以定义concat定义连接行为。对于所有参数,Lua选择metamethod的原则:如果第一个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metatable,和第二参数无关。否则第二个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metamethod,否则报错。
注意:Lua不关心这种混合类型(如:s = s + 8)

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.__add = Set.union;
--乘
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}
--报错输出:bad argument #1 to 'pairs'
--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修改
Set.mt.__metatable = "not your business";
s1 = Set.new{};
print(getmetatable(s1));
--报错输出:cannot change a protected metatable
--setmetatable(s1,{});

6.__index:当我们访问一个表的不存在的域,返回结果为nil,这是正确的,但并不一致正确。实际上,这种访问触发lua解释器去查找__index metamethod:如果不存在,返回结果为nil;如果存在则由__index metamethod返回结果。
由于__index metamethod在继承中的使用非常常见,所以Lua提供了一个更简洁的使用方式。__index metamethod不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数;当他是一个表的时候,Lua将在这个表中看是否有缺少的域。

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   nil
10  0
t = {};
local _t = t;
t = {};
local mt = {
    __index = function(t,k)
        print("*access to element "..tostring(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])
输出:*update to element 2 to hello
*access to element 2
hello
--不知道这个的应用场景
--create private index
local index = {};
local mt = {
    __index = function(t,k)
        print("*access to element "..tostring(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
--只读表
function readOnly(t)
    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

days = readOnly{"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
print(days[1]);
days[2] = "Noday";
输出结果:Sunday
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会自动删除那些失效的对象。垃圾回收器只能在确认对象失效之后才会进行收集;它是不会知道你对垃圾的定义的。
例子1:堆栈:有一个数组和指向栈顶的索引构成。你知道这个数组中有效的只是在顶端的那一部分,但Lua不那么认为。如果你通过简单的出栈操作提取一个数组元素,那么数组对象的其他部分对Lua来说仍然是有效的。同样的,任何在全局变量中声明的对象,都不是Lua认为的垃圾,即使你的程序中根本没有用到他们。这两种情况下,你应当自己处理它(你的程序),为这种对象赋nil值,防止他们锁住其他的空闲对象。
例子2:当你想在你的程序中对活动的对象(比如文件)进行收集的时候。可以在收集器中插入每一个新的对象。然而,一旦对象被插入收集器,它就不会再被收集!即使没有其他的指针指向它,收集器也不会做什么的。Lua会认为这个引用是为了阻止对象被回收的。
Weak表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。一个weak引用是指一个不被Lua认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak,对象将被收集,而那些weak引用将会被删除。Lua通过weak table来实现weak引用:一个weak tables是指所有引用都是weak的table。这意味着,如果一个对象只存在于weak tables中,Lua将会最终将它收集。
表有keys和values,这2者都可能包含任何类型的对象。在一般情况下,垃圾收集器并不会收集作为keys和values属性的对象。也就是说,keys和values都属于强引用类型,他们可以防止他们指向的对象被回收。在一个weak tables中,keys和values也可能是weak的。那意味着这里存在三种类型的weak tables:weak keys组成的tables,weak values组成的tables,以及纯weak tables类型,他们的keys 和values都是weak的。与table本身的类型无关,当一个keys或者value被收集时,整个的入口都将从这个table中消失。
注意:只有对象才可以从一个weak table中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如:如果我们在table中插入了一个数值型的key,它将永远不会被收集器从table中移除。当然,如果对应于这个数值型key的vaule被收集,那么它的整个入口将会从weak table中被移除。
关于字符串的一些细微差别:从上面的实现来看,尽管字符串是可以被收集的,他们仍然跟其他可收集对象有所区别。其他对象,比如tables和函数,他们都是显示的被创建。比如,不管什么时候当Lua遇到{}时,它建立了一个新的table。任何时候这个function()…end建立了一个新的函数(实际上是一个闭包)。从程序员的角度看,字符串是值而不是对象。所以,就像数值或布尔值,一个字符串不会从weak tables中移除(除非它所关联的vaule被收集)

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"};
function mem_loadstring(s)
    if results[s] then
        return results[s];
    else
        local res = loadstring(s);
        results[s] = res;
        return res;
    end
end

–设置默认值 local defaults = {}; –如果默认值没有weak的keys,它就会将所有的带默认值的tables设定为永久存在 setmetatable(default,{__mode = “k”}); local mt = {__index = function(t)return defaults[t] end} function setDefault(t,d) defaults[t] = d; setmetatable(t,mt); end

相关文章
相关标签/搜索