在 Lua 中,使用“.”符号时容易出现 __index 和命名空间的混淆。

我对使用 "." 的以下两种语法感到困惑。

  1. 根据我的理解,__index 在键值不存在于表中但存在于其元表中时被调用。那么为什么列表表现为调用 __index 然后将本身分配给 list.__index

    list = {}
    list.__index = list
    
    setmetatable(list, { __call = function(_, ...)
      local t = setmetatable({length = 0}, list)
      for _, v in ipairs{...} do t:push(v) end
      return t
    end })
    
    function list:push(t)
      if self.last then
        self.last._next = t
        t._prev = self.last
        self.last = t
      else
       self.first = t
       self.last = t
      end
      self.length = self.length + 1
    end
      .
      .
      .
    local l = list({ 2 }, {3}, {4}, { 5 })
    
  2. Window.mt 是否只是创建表格?为什么我们需要 Window = {} 作为一个命名空间?

    Window = {}  -- create a namespace
    Window.mt = {}  -- create a metatable
    Window.prototype = {x=0, y=0, width=100, height=100, }
    
    function Window.new (o)
      setmetatable(o, Window.mt)
      return o
    end
    
    Window.mt.__index = function (table, key)
      return Window.prototype[key]
    end
    
    w = Window.new{x=10, y=20}
    print(w.width)    --> 100
    
点赞
用户3677376
用户3677376

所以为什么表列表表调用 __index 然后将自己赋给 list.__index?

在你的代码中,列表表从来没有调用过 __index。然而,赋值部分是 Lua 中一种常见的惯用法(也称为“hack”),可以节省一些内存。概念上涉及四种不同的表:

  1. 列表对象(通过 {length=0} 在你的代码中创建的表)

  2. 包含 __index 字段的元表,用于在尝试访问对象中不存在的字段时修改列表对象的行为

  3. list 类,它包含所有列表对象的方法(如 push 方法),并且还用作列表对象的构造函数

  4. list 类的元表(包含 __call 字段),使得你可以像调用函数一样调用 list

    list objects separate metatable and index table

由于元表字段始终以两个下划线 (__) 开头,而正常方法通常不以此开头,因此你可以将元表字段和普通方法并排放置在单个表中而没有冲突。这就是这里发生的事情。list 类表也用作列表对象的元表。因此,使用此技巧,你可以节省通常需要用于独立元表的内存(此表在标题栏中以方括号显示 Lua 5.2 在 x86-64 Linux 上的字节数):

list objects with merged metatable and index table

Window.mt 是否只是创建了一个表?

不是,{} 创建了一个表。但是,将这个新表保存在 Window 表的键 "mt" 下,可能是为了让这个 Window "类" 的用户直接访问用于窗口对象的元表。仅根据你展示的代码,这不是绝对必要的,你可以使用本地变量代替。

为什么我们需要 Window = {} 作为名称空间?

原则上,你可以分别存储 Window.mtWindow.newWindow.prototype,但如果你有多个类似 Window 的 "类",这样做会变得笨重。这样你就可以避免名称冲突,并且使用 Window "类" 看起来更好看。

另一个原因可能是 require 只能从模块定义返回单个值,如果你想从模块中导出多个值(如 newmtprototype),则需要一个表将它们包装在一起(或使用全局变量,但这被认为是不好的风格)。

2014-12-17 11:58:56