Lua查找表元素过程(元表、__index方法是如何工作的)

本文深入解析Lua中的元表概念及其实现机制,通过具体示例解释__index元方法的工作原理,并提供Lua查找表元素的三步规则。

近日开始频繁使用Lua,发现身边有很多同学对元表的理解不太正确,于是把这块东西理了一下,分享出来


Lua的表本质其实是个类似HashMap的东西,其元素是很多的Key-Value对,如果尝试访问了一个表中并不存在的元素时,就会触发Lua的一套查找机制,也是凭借这个机制来模拟了类似“继承”的行为



举例说明:

tempTable = {}
print(tempTable.memberA) --这里试图打印tempTable并不存在的成员memberA
执行结果:nil
输出为nil的原因很简单,tempTable中并没有memberA这个成员,这符合我们平时对HashMap的认知。但对于Lua表,如果tempTable有元表,情况就不同了。


什么是元表:
元表像是一个“操作指南”,里面包含了一系列操作的解决方案,例如__index方法就是定义了这个表在索引失败的情况下该怎么办。


__index元方法:
很多人对此都有误解,这个误解是:如果A的元表是B,那么如果访问了一个A中不存在的成员,就会访问查找B中有没有这个成员。而这个理解是完全错误的,实际上,即使将A的元表设置为B,而且B中也确实有这个成员,返回结果仍然会是nil,原因就是B的__index元方法没有赋值。别忘了我们之前说过的:“元表是一个操作指南”,定义了元表,只是有了操作指南,但不应该在操作指南里面去查找元素,而__index方法则是“操作指南”的“索引失败时该怎么办”。这么说有点绕。所以:


举个栗子:)

father = {
	house=1
}
son = {
	car=1
}
setmetatable(son, father) --把son的metatable设置为father
print(son.house)
输出的结果是nil,但如果把代码改为
father = {
	house=1
}
father.__index = father -- 把father的__index方法指向自己
son = {
	car=1
}
setmetatable(son, father)
print(son.house)

输出的结果为1,符合预期


这样一来,结合上例,来解释__index元方法的含义:

在上述例子中,访问son.house时,son中没有house这个成员,但Lua接着发现son有元表father,注意:此时,Lua并不是直接在father中找名为house的成员,而是调用father的__index方法,如果__index方法为nil,则返回nil,如果是一个表(上例中father的__index方法等于自己,就是这种情况),那么就到__index方法所指的这个表中查找名为house的成员,于是,最终找到了house成员。
注:__index方法除了可以是一个表,还可以是一个函数,如果是一个函数,__index方法被调用时将返回该函数的返回值。


到这里,总结一下Lua查找一个表元素时的规则,其实就是如下3个步骤:


1.在表中查找,如果找到,返回该元素,找不到则继续

2.判断该表是否有元表(操作指南),如果没有元表,返回nil,有元表则继续

3.判断元表(操作指南)中有没有关于索引失败的指南(即__index方法),如果没有(即__index方法为nil),则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值

Lua 元表是一个普通的 Lua ,包含一组元方法,这些元方法Lua 中的事件相关联,事件发生在 Lua 执行某些操作时,例如加法、字符串连接、比较等。每个值都可具有元表元表定义了原始值在某些特定操作下的行为,可以通过在值的原中设置特定的字段来改变作用于该值的操作的某些行为特征。例如,当数字值作为加法的操作数时,Lua 检查其元表中的 `"__add"` 字段是否有个函数,如果有,Lua 调用它执行加法[^1][^2]。 #### 元表的基本操作 在 Lua 中,可以使用 `getmetatable` 函数来查询任何一个值的元表,使用 `setmetatable` 函数来替换掉 table 的元表。不过,在 Lua 代码中只能设置 table 的元表,其它类型值的元表只能在 C 代码中设置。每个 table 和 userdata 拥有独立的元表(多个 table 和 userdata 也可以共享一个相同的作它们的元表),其它所有类型的值,每种类型都分别共享唯一的一个元表Lua 在创建新 table 时默认不会创建元表,一个 table 也可以作为自己的元表[^5]。 ```lua -- 创建一个 local myTable = {} -- 创建一个元表 local myMetaTable = {} -- 设置元表 setmetatable(myTable, myMetaTable) -- 获取元表 local meta = getmetatable(myTable) print(meta == myMetaTable) -- 输出 true ``` #### 常见元方法 - **__index方法**:Lua 读取元素时,如果在中找不到该元素,会判断该是否有元表,如果没有元表,返回 `nil`,有元表则继续判断元表有没有 `__index` 方法,如果 `__index` 方法为 `nil`,则返回 `nil`;如果 `__index` 方法是一个,则重复上述查找规则;如果 `__index` 方法是一个函数,Lua 会以和键为参数调用该函数,并返回该函数的返回值[^4]。 ```lua local defaultTable = { a = 10, b = 20 } local myTable = {} local meta = { __index = defaultTable } setmetatable(myTable, meta) print(myTable.a) -- 输出 10 ``` - **__newindex方法**:当对中不存在的索引赋值时,Lua 会检查元表中是否有 `__newindex` 方法。如果有,则调用这个方法,而不进行赋值操作;如果没有,则直接进行赋值。 ```lua local myTable = {} local meta = { __newindex = function(t, k, v) print("Setting " .. k .. " to " .. v) end } setmetatable(myTable, meta) myTable.c = 30 -- 输出 Setting c to 30 ``` - **__add 元方法**:用于定义两个相加的行为。 ```lua local t1 = {1, 2, 3} local t2 = {4, 5, 6} local meta = { __add = function(tab1, tab2) local result = {} for i = 1, #tab1 do result[i] = tab1[i] + tab2[i] end return result end } setmetatable(t1, meta) setmetatable(t2, meta) local sum = t1 + t2 for _, v in ipairs(sum) do print(v) end ```
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值