Lua:什么时候可以使用冒号语法?

时间:2015-10-10 09:09:16

标签: lua

虽然我理解基本difference between . and :,但当Lua允许使用冒号语法时,我还没有完全弄明白。例如,像这样的东西确实有效:

s = "test"
-- type(s) is string.
-- so I can write a colon function for that type
function string:myFunc() 
  return #self 
end

-- and colon function calls are possible
s:myFunc()

然而,相同的模式似乎不适用于其他类型。例如,当我有table而不是string时:

t = {}
-- type(t) is table.
-- so I can write a colon function for that type
function table:myFunc() 
  return #self 
end

-- Surprisingly, a colon function call is not not possible!
t:myFunc() -- error: attempt to call method 'myFunc' (a nil value)
-- But the verbose dot call works
table.myFunc(t)

转到另一种类型:

x = 1
-- type(x) is number.
-- So I was expecting that I can write a colon function 
-- for that type as well. However, in this case even this
-- fails: 
function number:myFunc() 
  return self 
end
-- error: attempt to index global 'number' (a nil value)

我目前正试图理解这一点。

结论是否正确
  • 某些类型如string允许冒号 - 函数 - 定义冒号 - 函数调用。
  • 其他类型如table仅允许冒号 - 函数 - 定义,但不允许冒号 - 函数调用。
  • 其他类型如number也不允许。

这些差异究竟是什么原因?是否有所有类型的列表,显示它们支持哪种类型的冒号语法?可能是number案例的解决方法,允许写例如x:abs()

3 个答案:

答案 0 :(得分:5)

string上的第一个示例有效,因为所有字符串都共享相同的元表,并且它存储在名为string的表中。

来自string

  

字符串库在表string中提供其所有功能。它还为__index字段指向string表的字符串设置元表。因此,您可以在面向对象的样式中使用字符串函数。例如,string.byte(s,i)可以写为s:byte(i)

table上的第二个例子不起作用,因为每个表都有自己的元表,名为table的表只是表库的所有函数的集合。

数字之类的类型默认情况下不支持metatable。

答案 1 :(得分:4)

作为一个完整的Lua新手,我花了一段时间来理解@Yu Hao的回答,所以我会尝试为其他初学者添加一些细节。如果有任何问题,请纠正我。

据我所知,x:someFunc()之类的调用如果[*]:

有效
  • x有一个metatable
  • ,metatable有一个字段__index
  • 指向包含函数someFunc的表。

正如俞昊指出的那样,字符串会自动获得指向表string的元表,例如:

th> s = 'test'

th> getmetatable(s)
{
  __mod : function: 0x40c3cd30
  __index : 
    {
      upper : function: builtin#82
      rep : function: builtin#79
      split : function: 0x40ffe888
      gfind : function: builtin#87
      find : function: builtin#84
      reverse : function: builtin#80
      lower : function: builtin#81
      len : function: 0x40af0b30
      tosymbol : function: 0x40ffe8a8
      myFunc : function: 0x41d82be0 -- note: this comes from our custom function string:myFunc()
      dump : function: builtin#83
      byte : function: builtin#76
      char : function: builtin#77
      gmatch : function: builtin#87
      match : function: builtin#85
      sub : function: builtin#78
      gsub : function: builtin#88
      format : function: builtin#89
    }
}

因此,在这种情况下,s:myFunc()会自动运行。为了使用table的冒号语法,我们可以手动设置其metatable:

th> function enableColonForTable(t)
..>   meta = {__index = table}
..>   setmetatable(t, meta)
..> end

th> t = {}

th> enableColonForTable(t)

th> t:insert(1) -- works now!

另一个观察结果是,__index是否指向与该类型具有完全相同名称的表实际上并不重要。而不是meta = {__index = table},我们也可以这样做:

th> arbitraryScope = {}

th> function arbitraryScope:test() return "something" end

th> t = {}

th> setmetatable(t, {__index = arbitraryScope})
{}

th> t:test()
something   

这也是number案例的主要区别。虽然存在名为stringtable的现有表,但没有名为number的现有表。这就是为什么甚至定义例如function number:abs()之前失败了。但我们仍然可以使它发挥作用:

th> number = {}

th> function number:abs() return math.abs(self) end

th> x = -123

th> debug.setmetatable(x, {__index = number})
-123    

th> x:abs()
123 

请注意,我们必须在此处使用debug.setmetatable而不是setmetatable。两者之间的差异似乎是setmetatable仅为给定实例设置元表,而debug.setmetatable设置整个类型的元表。显然设置individual metatable for numbers is forbidden(并且反正意义不大)。这意味着(与表格不同)新构造的数字现在默认具有给定的metatable,因此这有效:

th> y = -42

th> y:abs()
42  

[*]更新

正如Tom Blodget所指出的,如果x:someFunc()本身作为命名空间,x也有效,即它是一个带有方法字段someFunc的表。例如,您可以table:insert(1)。但是现在命名空间(名为table的表)作为self传递,你可以在命名空间中添加数据:

th> print(getmetatable(table)) -- note: "table" does not have a metatable
nil 

th> table:insert(1) -- yet a colon syntax call works

th> table
{
  prune : function: 0x4156bde0
  getn : function: 0x41eb0720
  maxn : function: builtin#90
  remove : function: 0x41eb08c8
  foreachi : function: 0x41eb05b8
  sort : function: builtin#93
  concat : function: builtin#92
  unpack : function: builtin#16
  splice : function: 0x4156bdc0
  foreach : function: 0x41eb0688
  1 : 1
  pack : function: builtin#94
  insert : function: builtin#91
}

答案 2 :(得分:2)

补充答案:

首先,请注意函数是一个值(又名"first-class citizen")。

:是Lua中的三个索引运算符之一。索引操作符返回"字段"的值。来自可以索引的对象 - 无论是函数还是任何其他类型。

索引运算符按通用顺序排列:

  1. expression [ expression2 ]
  2. 表达式.标识符
  3. 表达式:标识符(参数列表)
  4. 后两者只是"语法糖"并且可以以上面的任何形式重写。

    你会使用第二个if"表达式2"将始终使用相同的字符串作为有效的Lua标识符,并且您希望对其进行硬编码。

    如果通过索引"标识符"返回的值,您将使用第三个;反对"表达"将永远是一个你想用"表达式"返回的值调用的函数。作为隐含的第一个参数。这样的函数调用称为"方法调用。"

    另请注意,语言/编译器并不关心/知道字段值是否为函数(如果您尝试调用的值不是,那么您在运行时会收到错误; ta功能)。它也不关心/知道函数是否是一种方法(如果你没有通过适当的参数,函数可能会按照你的意图行事)。

    现在,表达式值的类型必须是可以编制索引的任何类型。请注意,表达式没有编译时类型,因此如果表达式值是无法索引的类型,那也是运行时错误。可索引类型包括:table和具有__index元方法的任何对象。其他答案提供了这些细节。