Lua—元表和元方法

Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。

元表中的键对应着不同的 event; 键关联的那些值被称为元方法。 在上面那个例子中引用的事件为 “add” , 完成加操作的那个函数就是元方法。

可以用 getmetatable 函数 来获取任何值的元表
使用 setmetatable 来替换一张表的元表。在 Lua 中,你不可以改变表以外其它类型的值的元表

具体举个例子

local a = {}

function a.LoadFile()
    print("加载")
end

local b = {}
setmetatable(b,{__index = a})
b.LoadFile()

输出结果

lua 如何判断table 为空 lua table方法_Lua


声明一个table a,并在该表内声明一个方法,然后使用setmetatable函数将该表设定为table b的元表,可以理解成C#里的类继承

这里要注意的是如果每次使用setmetatable函数都要用上面的设置方法,那么每次设定元表时都会重新多声明一个表,浪费内存,可以写成这样

local a = {}

function a.LoadFile()
    print("加载")
end

a.__index = a;

local b = {}
setmetatable(b,a)
b.LoadFile()

在设定元表前先将table a的元表设定为自身,这样在设定table b的元表时就不用再多声明一个表

下面时一张元表可以控制的事件的完整列表
每个操作都用对应的事件名来区分。 每个事件的键名用加有 ‘__’ 前缀的字符串来表示; 例如 “add” 操作的键名为字符串 “__add”。 注意、Lua 从元表中直接获取元方法; 访问元表中的元方法永远不会触发另一次元方法。 下面的代码模拟了 Lua 从一个对象 obj 中获取一个元方法的过程:

rawget(getmetatable(obj) or {}, "__" .. event_name)

对于一元操作符(取负、求长度、位反), 元方法调用的时候,第二个参数是个哑元,其值等于第一个参数。 这样处理仅仅是为了简化 Lua 的内部实现 (这样处理可以让所有的操作都和二元操作一致), 这个行为有可能在将来的版本中移除。 (使用这个额外参数的行为都是不确定的。)

add

+ 操作。 如果任何不是数字的值(包括不能转换为数字的字符串)做加法, Lua 就会尝试调用元方法。 首先、Lua 检查第一个操作数(即使它是合法的), 如果这个操作数没有为 “__add” 事件定义元方法, Lua 就会接着检查第二个操作数。 一旦 Lua 找到了元方法, 它将把两个操作数作为参数传入元方法, 元方法的结果(调整为单个值)作为这个操作的结果。 如果找不到元方法,将抛出一个错误

sub

- 操作。 行为和 “add” 操作类似

mul

* 操作。 行为和 “add” 操作类似

div

/ 操作。 行为和 “add” 操作类似

mod

% 操作。 行为和 “add” 操作类似

pow

^ (次方)操作。 行为和 “add” 操作类似

unm

-(取负)操作。 行为和 “add” 操作类似

idiv

// (向下取整除法)操作。 行为和 “add” 操作类似

band

& (按位与)操作。 行为和 “add” 操作类似, 不同的是 Lua 会在任何一个操作数无法转换为整数时 (参见 §3.4.3)尝试取元方法

bor

l(按位或)操作。 行为和 “band” 操作类似。

bxor

~ (按位异或)操作。 行为和 “band” 操作类似

bnot

~ (按位非)操作。 行为和 “band” 操作类似

shl

~ (按位非)操作。 行为和 “band” 操作类似

shr

>> (右移)操作。 行为和 “band” 操作类似

concat

(连接)操作。 行为和 “add” 操作类似, 不同的是 Lua 在任何操作数即不是一个字符串 也不是数字(数字总能转换为对应的字符串)的情况下尝试元方法

len

# (取长度)操作。 如果对象不是字符串,Lua 会尝试它的元方法。 如果有元方法,则调用它并将对象以参数形式传入, 而返回值(被调整为单个)则作为结果。 如果对象是一张表且没有元方法, Lua 使用表的取长度操作。 其它情况,均抛出错误

eq

== (等于)操作。 和 “add” 操作行为类似, 不同的是 Lua 仅在两个值都是表或都是完全用户数据 且它们不是同一个对象时才尝试元方法。 调用的结果总会被转换为布尔量

lt

< (小于)操作。 和 “add” 操作行为类似, 不同的是 Lua 仅在两个值不全为整数也不全为字符串时才尝试元方法。 调用的结果总会被转换为布尔量

-le-

<= (小于等于)操作。 和其它操作不同, 小于等于操作可能用到两个不同的事件。 首先,像 “lt” 操作的行为那样,Lua 在两个操作数中查找 “__le” 元方法。 如果一个元方法都找不到,就会再次查找 “__lt” 事件, 它会假设 a <= b 等价于 not (b < a)。 而其它比较操作符类似,其结果会被转换为布尔量

index

索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。尽管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。(这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法)

newindex

索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。同索引过程那样, 这个事件的元方法即可以是函数,也可以是一张表。 如果是一个函数, 则以 table、 key、以及 value 为参数传入。 如果是一张表, Lua 对这张表做索引赋值操作。 (这个索引过程是走常规的流程,而不是直接索引赋值, 所以这次索引赋值有可能引发另一次元方法。)一旦有了 “newindex” 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)

call

函数调用操作 func(args)。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。

__call的具体使用方法

local a = {}

function a.LoadFile()
    print("加载",)
end

a.__index = a
a.__call = function (...)
    local i = {}
    setmetatable(i,a)
    return i
end
setmetatable(a, a)

b = a()
b.xxx = 0
b:LoadFile()

使用__cal时需要设置__index为自身表,并且在写完__call方法号要设定元表为自身

a.__index = a
a.__call = function (...)
    local i = {}
    setmetatable(i,a)
    return i
end
setmetatable(a, a)

结果

lua 如何判断table 为空 lua table方法_字符串_02