虽然我理解基本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()
?
答案 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 __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
案例的主要区别。虽然存在名为string
和table
的现有表,但没有名为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中的三个索引运算符之一。索引操作符返回"字段"的值。来自可以索引的对象 - 无论是函数还是任何其他类型。
索引运算符按通用顺序排列:
[
expression2 ]
.
标识符:
标识符(
参数列表)
后两者只是"语法糖"并且可以以上面的任何形式重写。
你会使用第二个if"表达式2"将始终使用相同的字符串作为有效的Lua标识符,并且您希望对其进行硬编码。
如果通过索引"标识符"返回的值,您将使用第三个;反对"表达"将永远是一个你想用"表达式"返回的值调用的函数。作为隐含的第一个参数。这样的函数调用称为"方法调用。"
另请注意,语言/编译器并不关心/知道字段值是否为函数(如果您尝试调用的值不是,那么您在运行时会收到错误; ta功能)。它也不关心/知道函数是否是一种方法(如果你没有通过适当的参数,函数可能会按照你的意图行事)。
现在,表达式值的类型必须是可以编制索引的任何类型。请注意,表达式没有编译时类型,因此如果表达式值是无法索引的类型,那也是运行时错误。可索引类型包括:table和具有__index
元方法的任何对象。其他答案提供了这些细节。