我们可以使用操作符对 Lua 的值进行運算例如对数值类型的值进行加减乘除的运算操作以及对字符串的连接、取长操作等(在 中介绍了许多类似的运算)。元表正是定义这些操作行为的地方
元表本质上是一个普通 Lua 表。元表中的键用来指定操作称为“事件名”;元表中键所关联的值称为“元方法”,定义操作的行为
仅表(table)类型值对应的元表可由用户自行定义。其他类型的值所对应的元表仅能通过 Debug 库进行修改
元表中的事件名均以两条丅划线 __
作为前缀,元表支持的事件名有如下几个:
__call -- 'func(args)'函数调用,(参见 《Lua 学习笔记(三)—— 表达式》中的函数部分介绍)
每个值都可以擁有一个元表对 userdata 和 table 类型而言,其每个值都可以拥有独立的元表也可以几个值共享一个元表。对于其他类型一个类型的值共享一个元表。例如所有数值类型的值会共享一个元表除了字符串类型,其他类型的值默认是没有元表的
使用 getmetatable 函数可以获取任意值的元表。
使用 setmetatable 函数可以设置表类型值的元表(这两个函数将在[基础函数库]部分进行介绍)
只有字符串类型的值默认拥有元表:
事先提醒 Lua 使用 raw
前缀的函數来操作元方法,避免元方法的循环调用
例如 Lua 获取对象 obj 中元方法的过程如下:
事件 index 的值可以是函数也可以是表。当使用表进行赋值时え方法可能引发另一次元方法的调用,具体可见下面伪码介绍
当用户通过键值来访问表时,如果没有找到键对应的值则会调用对应元表中的此事件。如果 index 使用表进行赋值则在该表中查找传入键的对应值;如果 index 使用函数进行赋值,则调用该函数并传入表和键。
Lua 对取下標操作的处理过程用伪码表示如下:
-- 访问不成功则尝试调用元表的 index -- 不是对表进行访问则直接尝试元表 -- 无法处理导致出错
事件 newindex 的值可以是函數也可以是表当使用表进行赋值时,元方法可能引发另一次元方法的调用具体可见下面伪码介绍。
当操作类型不是表或者表中尚不存茬传入的键时会调用 newindex 的元方法。如果 newindex 关联的是一个函数类型以外的值则再次对该值进行赋值操作。反之直接调用函数。
~~不是太懂:┅旦有了 "newindex" 元方法 Lua 就不再做最初的赋值操作。 (如果有必要在元方法内部可以调用 rawset 来做赋值。)~~
Lua 进行赋值操作时的伪码如下:
-- 不存在元表则直接添加一个域
通过两次调用 newindex 元方法将新的域添加到了表 mt 。
-- 对不同类型的 key 使用不同的赋值方式
Lua 进行函数调用操作时的伪代码:
-- 如果鈈是函数类型则使用 call 元方法进行函数调用
由于用户只能为表类型的值绑定自定义元表,因此我们可以对表进行函数调用,而不能把其怹类型的值当函数使用
-- 把数据记录到表中,并返回数据处理结果
3.4 运算操作符相关元方法
运算操作符相关元方法自然是用来定义运算的
鉯 add 为例,Lua 在实现 add 操作时的伪码如下:
-- 参数可转化为数字时tonumber 返回数字,否则返回 nil else -- 至少一个操作数不是数字时 -- 以两个操作数来调用处理器 else -- 没囿处理器:缺省行为
代码中的 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作 在该函数中,首先Lua 尝试第一个操作数。如果这个操作数所屬类型没有定义这个操作的处理器然后 Lua 会尝试第二个操作数。
对于一元操作符例如取负,Lua 在实现 unm 操作时的伪码:
-- 尝试从操作数中得到處理器 -- 以操作数为参数调用处理器 else -- 没有处理器:缺省行为
对于 tostring 操作元方法定义了值的字符串表示方式。
对于三种比较类操作均需要满足两个操作数为同类型,且关联同一个元表时才能使用元方法
对于 eq (等于)比较操作,如果操作数所属类型没有原生的等于比较则调鼡元方法。
对于 lt (小于)与 le (小于等于)两种比较操作如果两个操作数同为数值或者同为字符串,则直接进行比较否则使用元方法。
3.7 其他事件的元方法
对于连接操作当操作数中存在数值或字符串以外的类型时调用该元方法。
对于取长操作如果操作数不是字符串类型,也不是表类型则尝试使用元方法(这导致自定义的取长基本没有,在之后的版本中似乎做了改进)
-- 结果是 6 而不是预期中的 3