lua table的__index ,setmetable()的一些操作

近日开始研究Lua,在元表的使用上照猫画虎地搞了两下,实现了“面向对象”,但究其本质却略有不解,后咨询牛哥得解,特此记录。


Lua的表本质其实是个类似HashMap的东西,其元素是很多的Key-Value对,如果尝试访问了一个表中并不存在的元素时,就会 触发Lua的一套查找机制,也是凭借这个机制,才能够实现“面向对象”的。


举例说明:

  1. tempTable = {}  
  2. print(tempTable.memberA) --这里试图打印tempTable并不存在的成员memberA 

执行结果:nil
输出为nil的原因很简单,tempTable中并没有memberA这个成员,这符合我们平时对HashMap的认知。但对于Lua表,如果tempTable有元表,情况就不同了。


什么是元表:
我的理解中,元表像是一个备用查找表,说白了假设表A的元表是B,那么如果在A中找不到的东西就会尝试在B中去找。


__index元方法:
按照之前的说法,如果A的元表是B,那么如果访问了一个A中不存在的成员,就会访问查找B中有没有这个成员。这个过程大体是这样,但却不完全是这样,实际上,即使将A的元表设置为B,而且B中也确实有这个成员,返回结果仍然会是nil,原因就是B的__index元方法没有赋值。按照我的理解,__index方法是用来确定一个表在被作为元表时的查找方法。这么说有点绕。所以:


举个栗子:)

  1. father = {  
  2.     house=1  
  3. }  
  4. son = {  
  5.     car=1  
  6. }  
  7. setmetatable(son, father) --把son的metatable设置为father  
  8. 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,于是此时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 中 `__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` 都能极大地增强程序的功能性和灵活性。它可以用来简化复杂的数据查询逻辑或是构建面向对象编程模型的基础组件之一。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值