我是Lua的新手,我正在研究this video。在38:56,代码如下:
Sequence = {}
function Sequence:new()
local new_seq = {last_num = 0}
self.__index = self
return setmetatable(new_seq, self)
end
function Sequence:next()
print(self.last_num)
end
我的理解是self
相当于Sequence
,而self
被设置为new_seq
的元数据,而这个元数据__index
}也是self
。
而last_num
是表格new_seq
的一个关键,但不是self
的一个关键字,next
函数的定义如何写入self.last_num
因为处理last_num
是self
的一个关键?
此外,在调用setmetatable
之前,有self.__index = self
,我认为只有metatable有__index
作为特殊键,但在调用之前
setmetatable
,Sequence
只是一张常规表格,它还不是一个表格,它怎么会有__index
?
答案 0 :(得分:4)
简短版本:
我的理解是
self
等同于Sequence
...
self
是由方法样式函数定义产生的隐式参数。在函数中,它将引用作为该函数的第一个参数传入的任何值。
(关于自我的其余问题源于同样的困惑。)
我认为只有metatable有
__index
作为特殊键...
metatable只是一张桌子。 __index
只是一个像任何其他键一样的键,您可以在任何表上定义具有该名称的字段。只有当对表的查找失败并且Lua注意到该表具有附加的元表时,名为__index
的元表的字段具有特殊含义 - 因为Lua将寻找处理程序
__index
只是处理程序的另一个特例(因为它很常见),__index = some_other_table
大致相当于__index = function( table, key ) return some_other_table[key] end
- 即"如果some_other_table
为空,请table[key]
查看那里"。 (如果您无法跟踪发生的情况,那么使用长版本和print
可能会有所帮助。)
长版本,对代码进行去糖处理并详细介绍:
定义function foo:bar( ... )
与function foo.bar( self, ... )
相同(自动选择名称self
,大致类似于其他语言中的this
)。此外,function foo.bar( ... )
与foo.bar = function( ... )
相同。这意味着上面的代码与......相同。
Sequence = {}
Sequence.new = function( self )
local new_seq = { last_num = 0 }
self.__index = self
return setmetatable( new_seq, self )
end
Sequence.next = function( self )
print( self.last_num )
end
......相当于......
Sequence = {
new = function( self )
local new_seq = { last_num = 0 }
self.__index = self
return setmetatable( new_seq, self )
end,
next = function( self )
print( self.last_num )
end,
}
因此,从本质上讲,它定义的是一个包含两个函数的表,每个函数都使用一个参数。这两个函数中的第二个next
非常简单:它只打印它传递的任何表的字段last_num
的内容(使用名称self
来引用它)。
现在,就像定义一样,还有一些:
- 用于调用的语法糖。致电foo:bar( ... )
会转换为foo.bar( foo, ... )
,因此当您some_sequence
并说出some_sequence:next( )
时,会发生一次电话some_sequence.next( some_sequence )
- :
- 定义的语法引入了一个额外的隐藏参数,:
- 调用语法填充了额外的参数。通过这种方式,您作为方法处理的函数可以访问您作为对象处理的表,并且一切都很顺利。
new
函数更复杂一些 - 我将其重写为另一个等效形式,以便于阅读:
function Sequence.new( self )
self.__index = self
return setmetatable( { last_num = 0 }, self )
end
因此,无论传入哪个表,它都会将该表分配给同一个表的字段__index
,并返回一个新表,该旧表设置为metatable。 (是的,这件事令人困惑......不用担心,继续阅读。)为了了解其原因和方法,以下是一个例子:
如果您说some_sequence = Sequence:new( )
,您将拥有以下结构:
some_sequence = { last_num = 0 } -- metatable:-> Sequence
Sequence = { new = (func...), next = (func...), __index = Sequence }
现在,当您说some_sequence:next( )
时,这会转换为电话some_sequence.next( some_sequence )
。但some_sequence
没有字段next
!因为some_sequence
有一个metatable,Lua去看看 - 在这种情况下,metatable是Sequence
。作为查找(或"索引")操作"失败" (它会返回nil
),Lua在metatable的字段__index
中查找处理程序,找到一个表(Sequence
再次)并重新尝试查找一个而不是(找到我们定义的next
函数。)
在这种情况下,我们可以等效地编写Sequence.next( some_sequence )
(但一般情况下,您不想 - 或者可以 - 手动解析这些引用)。如上所述,next
只打印它收到的表的字段last_num
的值 - 在这种情况下它得到some_sequence
。再说一遍,一切都很顺利。
更多评论(还有另一个例子):
对于一个介绍性的例子,代码比必要的更容易弯曲和脆弱。这是另一个版本(它们不相同,实际上表现不同,但应该更容易理解):
Sequence = { }
Sequence.__index = Sequence
function Sequence.new( )
return setmetatable( { last_num = 0 }, Sequence )
end
function Sequence:next( )
print( self.last_num )
end
当您运行以下内容时,您拥有的版本和此版本都将打印0
:
some_sequence = Sequence:new( )
some_sequence:next( )
(我已经在上面描述了当您为代码执行此操作时发生的事情,比较并尝试在阅读之前弄清楚我的版本会发生什么。)
这也将为两个版本打印0
:
sequences = { [0] = Sequence }
for i = 1, 10 do
local current = sequences[#sequences]
sequences[#sequences+1] = current:new( )
end
local last = sequences[#sequences]
last:next( )
引擎盖下发生的事情在两个版本中都有很大差异。这是sequences
代码的样子:
sequences[0] = Sequence -- with __index = Sequence
sequences[1] = { last_num = 0, __index = sequences[1] } -- metatable:->Sequence
sequences[2] = { last_num = 0, __index = sequences[2] } -- metatable:->sequences[1]
sequences[3] = { last_num = 0, __index = sequences[3] } -- metatable:->sequences[2]
...
这就是我的版本:
sequences[0] = Sequence -- __index = Sequence, as set at the start
sequences[1] = { last_num = 0 } -- metatable:->Sequence
sequences[2] = { last_num = 0 } -- metatable:->Sequence
sequences[3] = { last_num = 0 } -- metatable:->Sequence
...
(如果您在上面的循环中改为sequences[#sequences+1] = Sequence:new( )
,那么您的代码也会生成此代码。)
在我的版本中,来电last:next( )
无法找到next
,查看metatable(Sequence
),找到__index
字段(同样,Sequence
})并找到next
,然后如上所述继续调用它。
根据您的版本,来电last:next( )
无法找到next
,查看metatable(sequences[9]
),找到__index
字段(sequences[9]
) ,找不到next
,因此查看metatable(sequences[9]
,sequences[8]
),找到__index
字段(sequences[8]
),但未能找到next
,然后查看metatable ...(直到我们到达sequences[1]
)...找不到next
,查看metatable(Sequence
),找到__index
字段(Sequence
),最后找到next
,然后继续调用。 (这就是我说它很难遵循的原因......)
您拥有的代码实现了基于原型的OOP,具有所有优缺点。正如您所见,查找遍历整个链,这意味着您可以定义函数sequences[5].next
来执行其他操作,随后sequences[5]
到sequences[10]
会找到其他函数。这可能非常有用 - 不需要所有样板来定义新类来更改某些功能,只需调整一个对象并像类一样使用它。 (如果你不小心这样做,这也会很烦人。)
我的版本实现了与许多其他语言中看到的基于类的OOP更接近的东西。 (您不能一次意外地覆盖多个对象的方法。)这两者(以及Lua中的OOP的许多其他方法)的共同之处在于定义了与a具有相同名称的对象的字段。方法将隐藏该方法并使其无法访问。 (如果您定义some_sequence.next
,说some_sequence:next( )
或some_sequence.next( some_sequence )
会立即找到您定义的next
,Lua不会费心去查看metatable等等。 )