Lua 学习笔记——metatable和__index

最近学习lua今天突然对metatable和__index之间的关系有点不清楚,上网查了下加深了对metatable的理解,记录一下

还是用代码来解释,下面有图解,metatable和__index主要用于继承相关,有两种方式:

test.lua

local test = {}

function test:new()
    self.__index = self
    return setmetable({}, self)
end

function test:say()
    print("111")
end

local t1 = test:new()
t1.say()

local t2 = t1:new()
t2.say()

return test

test2.lua

local test2 = {}

test2.__index = test2

function test2:new()
    return setmetatable({}, self)    -- 这里的self 替换成 test2 试试
end

function test2:say()
    print("222")
end

local t3 = test2:new()
t3.say()

local t4 = t3:new()
t4.say()            -- 这一步会报错

return test2

上面的代码test.lua中t1,t2都能访问到new和say方法,但是test2中只有直接通过test2:new()生成的对象(t3)才能访问new和say方法,而通过test2:new()生成的子对象生成的对象(t4)则访问不到new和say方法

要解释上述问题,首先要知道lua从table中查找一个key时的流程,当从表t中查找k时:

  1. t中是否有k,有则返回,无则第2步
  2. t是否有metatable,无则返回nil,有则第3步
  3. t的metatable中是否有__index方法,无则返回nil,有则查找 __index对应的table或者方法

所以我们一步步分析test.lua中的代码:

local test = {}

function test:new()
    ...
end

function test:say()
    ...
end

这一句就是创建了一个table,我们用方块表示table则就是下面这样:
test有两个方法为new和say

接下来实例化一个对象:

local t1 = test:new()

这里调用test:new(),相当于test.new(test),函数中的self为test,所以这一步改变了test表,结果用图表示为:

图中椭圆表示__index函数,__index指向test自身,可以看到这时test表和上面不一样,而t1是一个新生成的表,这个表的metable为test,但是t1本身没有其他内容

再来看最后一步

local t2 = t1:new()

此时结果为:
这里写图片描述
这一步new函数中的的self为t1,所以最后改变了t1的__index指向t1,生成新的表t2的metatable为t1,由于t1和t2都有metatable并且metatable中都有__index方法所以都可以访问test中的new和say方法,即使它们自身没有这两个方法

再来看test2.lua中有什么不同,同理按照上面的分析有三个图,就不一一解释了
这里写图片描述

这里写图片描述

这里写图片描述

唯一不同的地方就在于__index方法的设置,在test2.lua中 __index只在test2表中设置,在继承过程中,并没有设置对应的__index方法,所以t3可以访问到new和say,但是t4则不可以

不过由此引出我一个问题,我们可以看到lua中查找不存在的k时,最终起作用的其实是__index方法,类似的还有操作符元方法等,但是却要先找metatable,然后找metatable里面的这些元方法,metatable在这里是相当于一个代理一样的作用,那么为什么不能直接在自身的表中设置,为什么要有metatable这个中间表??

猜想一:可能是模块化的考虑也就是把这些元方法集中在一起

  • 但是lua中有一个常用的用法就是 __index指向自身,上述例子就是这样,那么这个table里面本身就有元方法和自己的数据,而lua也没有说不推荐这么做,所以也不符合模块化的考虑

猜想二:可能是为了多个table方便共享同一个metatable

  • 问题是直接在自身table中设置__index指向同一个metatable也可以达到这种效果

猜想三:刚刚想到如果没有metatable的话,直接用__index指向要设置的元表,那么如果我想把自身作为基类,用__index指向自身,就会陷入死循环

  • 不知道是不是因为这点才引入了metatable这么一个中间代理

所以不清楚这部分的设计理念是什么,如果有知道的可以留言下

### Lua 中 `__index` 方法的用法 在 Lua 中,metatable)是一种强大的机制,用于定义对象的行为。当尝试访问一个键而该键不存在时,`__index` 方法会被触发[^1]。它可以通过两种方式设置: #### 1. 使用函数作为 `__index` 如果将一个函数赋值给 `__index` 字段,则每次访问未找到的键时都会调用这个函数。此函数接受两个参数:第一个是原始本身,第二个是要查找的键。 ```lua local mt = {} mt.__index = function(table, key) return "Key not found: " .. tostring(key) end local myTable = setmetatable({}, mt) print(myTable.someNonExistentKey) -- 输出: Key not found: someNonExistentKey ``` #### 2. 使用另一个作为 `__index` 更常见的做法是将另一个赋值给 `__index` 字段。这样,在原中找不到某个键时,Lua 将自动在这个替代中继续寻找对应的值。 ```lua local defaultValues = {a=1, b=2} local mt = {__index = defaultValues} local myTable = setmetatable({b=3}, mt) -- 访问存在的键 print(myTable.a) -- 输出: 1 (来自defaultValues) print(myTable.b) -- 输出: 3 (覆盖了默认值) -- 访问不存在的键 print(myTable.c) -- 输出: nil (既不在myTable也不在defaultValues中存在) ``` 这种模式非常有用,尤其是在实现继承关系或者提供默认配置的情况下[^2]。 #### 结合 C API 的例子 通过 Lua 的 C API 可以更加灵活地操作带有 `__index` 方法的对象。例如,可以利用 `lua_gettable` 函数来模拟 `__index` 行为: ```c #include <lauxlib.h> #include <lua.h> int l_my_index(lua_State* L){ // 获取 table key 参数 lua_pushvalue(L, 2); /* duplicate the key */ lua_rawget(L, lua_upvalueindex(1)); /* lookup in 'parent' */ if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushstring(L, "Not Found"); } return 1; } void setup_metatable(lua_State *L){ luaL_newmetatable(L, "MyType"); lua_pushcfunction(L, l_my_index); lua_setfield(L, -2, "__index"); lua_pop(L, 1); } ``` 在此代码片段中,我们创建了一个自定义类型的,并为其设置了 `__index` 方法。每当尝试读取未知字段时,C 函数 `l_my_index` 被执行并试图从父级结构检索数据[^3]。 ### 总结 无论是纯 Lua 还是在扩展库中的应用,理解如何正确运用 `__index` 都能极大地增强程序的功能性灵活性。它可以用来简化复杂的数据查询逻辑或是构建面向对象编程模型的基础组件之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值