怎么"自我"可以访问本地表的密钥?

时间:2015-10-25 22:31:38

标签: lua

我是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_numself的一个关键?

此外,在调用setmetatable之前,有self.__index = self,我认为只有metatable有__index作为特殊键,但在调用之前 setmetatableSequence只是一张常规表格,它还不是一个表格,它怎么会有__index

1 个答案:

答案 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等等。 )