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有什么好相加的啊!");
end
local 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