Lua中的元表(metatable)与元方法(metamethod)

来源:互联网 发布:安卓转java工具 编辑:程序博客网 时间:2024/04/29 07:49

1.知道为什么1 + 1 = 2吗?

为什么在Lua中,1+1会等于2呢?为什么数字和数字相加是合法的,为什么table和table相加就会报错?大家有想过这些问题吗?


没错,规则,这一切都只是规则而已,Lua规定了数字之间可以进行加减乘除,而table之间则不可以。这是因为并没有table和table相加的概念。而在Lua中,进行这些规则限定的秘密就在于元表和元方法。

 

2.元方法

元方法,听起来很深奥,其实它就是格子类型变量之间进行特殊操作的函数。比如,数字的相加,它可能仅仅是一个函数。比如:1+1  ,在底层里,它可能是这样的:add(1, 1)。而add函数就是用来计算两个数字间相加的结果。再如:10X15,它可能是这样的:mul(10, 15)。mul函数就能返回两个数字相乘的结果。(可能这例子不太恰当,但就是这么个意思~)

 

最后,如果是两个table呢?

local t1 = {};local t2 = {};t1 + t2;

它可能就是这样的:????

没错,Lua中不存在可以计算两个table相加的函数,也就是说,不存在这样的元方法。


3.元表

元表本身并没有什么作用,它是用来存放元方法的一个table。Lua中的每一个值都有或者可以有一个元表,table和userdata可以各种拥有独立的元表。但是,其他类型的值就只能共享其类型所属的元表,比如,数字,所有的数字都共用一个元表。


4.改变规则

如果说,我们就是希望将两个table进行相加呢?

试试看,如下代码:

local t1 = {};local t2 = {};local result = t1 + t2;

直接运行肯定报错的。

 

因此,为了满足我们这种需求,Lua允许我们修改元表。一个元表,其实就是一个table值,所以,我们只需要新建一个table,添加元方法即可。比如加法运算的元方法就是:__add,这是Lua规定的。只要某个值的元表里含有__add这个元方法,那就可以使用+号进行运算。

如下代码:

-- 创建一个元表local mt = {};mt.__add = function(t1, t2)    print("两个table相加的结果就是...神经病啊!table有什么好相加的啊!");endlocal t1 = {};local t2 = {};    -- 给两个table设置新的元表setmetatable(t1, mt);setmetatable(t2, mt);   -- 进行加法操作local result = t1 + t2;

  • 首先,创建了一个table变量mt,给这个table新增一个元素__add,这个table就拥有了作为元表的资格了。

  • 然后,创建两个新的table变量,使用setmetatable函数给table设置新的元表,此时,两个table变量就以mt作为元表了。

  • 最后,对t1和t2进行加法操作,这时就会从元表中查找__add元方法,如果找到的话,就调用这个元方法对两个变量进行加法操作。

 

输出结果如下:

[LUA-print] 两个table相加的结果就是…神经病啊!table有什么好相加的啊!

 

就是这么简单,元表和元方法其实就是给Lua里的值设定一些操作,比如加法、减法之类的,让我们可以对这些操作自定义。

 

不过,有几点要特别注意的:

  • 创建一个新的table变量时,它是不存在元表的(可以用getmetatable函数获取某个对象的元表,就能知道这个对象有没有元表存在了)。

  • 在Lua中,只能设置table的元表,其他类型的值的元表,只能通过C代码来完成。


5. 元表(metatable)小例子之算术类元方法

1)元方法名

Lua其实已经规定好了各种算术操作符的元方法名字,如:

  • __add:加法

  • __sub:减法

  • __mul:乘法

  • __div:除法

  • __unm:相反数

  • __mod:取模

  • __pow:乘幂

只要在自定义元表的时候,给这些元方法名赋予新的函数就可以实现自定义操作了。

2)例子

开始举例吧,我们新建一个自定义的元表(也就是一个table变量),用来定义一些操作:

-- 创建一个元表local mt = {};mt.__add = function(s1, s2)    local result = "";    if s1.sex == "boy" and s2.sex == "girl" then        result = "完美的家庭。";    elseif s1.sex == "girl" and s2.sex == "girl" then        result = "哦呵呵";    else        result = "蛇精病"    end          return result;end

其实这和之前的例子基本一样,只是多说一次而已,使用方式如下:

-- 创建两个table,可以想象成是两个类的对象local s1 = {    name = "Hello",    sex = "boy",};  local s2 = {    name = "Good",    sex = "girl",};   -- 给两个table设置新的元表setmetatable(s1, mt);setmetatable(s2, mt);    -- 进行加法操作local result = s1 + s2;   print(result);

其实我们可以把s1和s2当成是类的对象,实际上Lua也可以模拟类的结构。

输出结果如下:

[LUA-print] 完美的家庭。


6. 元方法的一些零散知识点

1)两个具有不同元表的值进行算术操作(比如加法)

之前举例的时候,两个table相加,这两个table都是具有相同的元表的,所以没有任何问题。

那么,如果两个table或者两个进行相加操作的值,具有不同的元表呢?

对于这种情况,Lua是这样处理:

  • a.如果第一个值有元表,就以这个元表为准

  • b.否则,如果第二个值有元表,就用第二个值的元表

  • c.如果两个值都没有元表,或者没有对于的元方法,那么,就会报错

 

2)关系类的元方法

除了加法减法这些算术类的操作之外,大于小于等这些关系类的操作也是有元方法的:

  • __eq:等于

  • __lt:小于

  • __le:小于等于

如果对两个具备不同元表的值进行这些比较操作,就会报错,一定要注意,这和加减法的规则不一样。


其实想想也很有道理,元表都不一样了,怎么去判断大小呢?判断大小是要有规则的。

 

比如,在军队里,中尉的职位肯定是小于上校的职位。

然后,到了外星世界呢(假设有外星人)?说不定中尉是大于上校的。

所以,地球人和外星人是不能用同一种方式进行军衔比较的。

 

当然,大家也许会说:那地球人和外星人也不能用同一种方式进行加法操作啊!

没错,但是,Lua就是这么规定的!

 

最后,比较特殊的,进行“等于”操作,是不会报错的,哪怕是具有不同元表的值进行等于操作。

其实这想想也是很有道理,地球人等于外星人吗?不等于。在外星那边,外星人也会得到一样的答案,即使判断标准不同。

 

3)保护元表

我们都知道,通过setmetatable和getmetatable可以分别设置和获得元表。但是,如果我们不希望元表被修改或者被看到呢?比如某天你当了主程,你写了一个很牛的模块,因为某些原因要交给一个刚毕业不到30年的学生去修改。你很肯定你的某个值的元表是不能被改动的,那你就可以把它保护起来了。

 

我们可以给元表的__metatable字段赋值,比如:mt.__metatable = “你别碰它的元表,否则过了10年的试用期之后,你就等着走人吧!”

然后,如果那个毕业不到30年的学生真的不小心去修改你的元表的话:

print(getmetatable(s1));setmetatable(s1, mt);

将会输出类似以下的日志:

你别碰它的元表,否则过了10年的试用期之后,你就等着走人吧!

cannot change protected metatable




0 0
原创粉丝点击