类和方法

lua-users home
wiki

[!] 版本通知:本教程使用 Lua 4.0 中的标签和标签方法的概念,这些概念在 Lua 5.0 中已被元表和元方法取代。请参阅 使用元表实现 Lua 类

类表

表可以作为关联数组使用。此功能允许我们将函数存储在类表中,即使用函数名映射到函数。

A = {}

function A:add(x,y)
  return x+y
end

print( A:add(1,2) )  -- prints 3
在这个例子中,我们只能有一个类 A 的实例。如果我们想创建另一个实例,我们可以创建一个表 B(例如,B={}),并将所有方法从 A 复制到 B 中。

标签方法

可以通过使用 Lua 的标签方法来创建类的多个实例。标签方法可以用来将来自类实例的函数请求重定向到类函数表。即,我们不再需要将所有类函数复制到类的每个实例中。例如:

-- settag() returns a table "{}" which has been tagged with a new tag value
A = settag({},newtag())

-- use the index tag method to redirect a request for a function to the 
-- class function table
settagmethod(tag(A),"index", function(t,f) return %A[f] end)

function A:new(x,y)  -- create an instance of class A
  local t = {x=x,y=y}
  settag(t,tag(A))  -- tag the new table to tell it what its class type is
  return t
end

function A:add()
  print (self.x+self.y)
end

function A:sum(a)
  assert(tag(self)==tag(a)) -- check they are same class
  print (self.x+a.x,self.y+a.y)
end

a = A:new(7,9)  -- new instance of A
a:add()

b = A:new(2,4)  -- new instance of A
b:add()

a:sum(b)  -- "sum" of 2 instances
然而,示例中存在一些问题。
settagmethod(tag(A),"index", function(t,f) return rawget(%A,f) end)
a.number = 123
a:number()  -- try and call x
这会导致错误,因为我们试图调用一个数字。
a.sum = 7
print( a.sum )  -- should this print <function> or 7 ? (it prints 7)

解决命名冲突

这个问题无法解决,因为调用请求不会告知标签方法需要哪种类型。我们可以选择一个优先级,例如,类实例值优先于函数表值。(这样做,我们可以支持函数重载。)

如果函数表优先,我们可以使用gettable 标签方法来首先检查函数表 A 中是否存在方法,例如:

settagmethod(tag(A), "gettable", 
          function(t,k)
            if rawget(%A,k) then
              return rawget(%A,k)
            else
              return rawget(t,k)
            end
          end )

统一方法:这个问题在Sol(由 Edgar Toernig 开发的 Lua 分支)中通过引入统一方法得到了解决。在这里,一个函数/方法表与一个标签相关联。表查找发生时会发生什么取决于查找是如何调用的。即,a.fooa:foo 有不同的含义。a.foo 查找名为 "foo" 的成员,而 a:foo 在标记的函数表中查找名为 "foo" 的方法。因此,您可以拥有两个成员,一个数据成员和一个函数成员,它们可以和谐共处,不会发生冲突。

为什么要将方法和“数据”放在不同的命名空间中?Sol 的统一方法很酷,它允许将方法和数据分开,并且提供了一个比标签方法更容易的系统。但是我认为它的主要目的是允许在不使每个表都包含自己的副本的情况下实现类方法。如果答案是因为某些类实例需要允许最终用户直接添加任意数据字段,我会说这可能不是好的设计,操作应该作为独立函数而不是类方法来实现,或者字段访问应该通过受控接口来完成。(我查看了PythonDictionaries。这不是试图模拟 Python 中的某些接口而不是提供适合 Lua 的接口的结果吗?)--JohnBelmonte

我不确定你关于受控接口的替代实现是什么。这会解决t.foo / t:foo问题(并允许存储任意数据)吗?它看起来像现有的 Lua 标签方法系统,或者修改后的?我不认为这仅仅是试图模拟 Python 的结果,这里存在冲突。即你的实现是有限制的。我认为元机制应该足够灵活以实现这样的功能。这是实现风格的问题吗?可以通过使用独立函数来避免这个问题,但我认为这不太整洁(在这个例子中,与 Python 不同)--NDT

以用 C++ 编写的容器为例。字段访问是受控的——要么通过operator[],要么通过专用的成员函数。字段访问和非字段访问之间没有歧义。Lua 的标签方法是有限制的。你无法区分用于访问字段的table[x]和用于调用方法的table.x / table:x。解决你的PythonDictionaries问题的简单方法是放弃使用原生表语法进行元素访问,而依赖于getset等方法。这将使实现变得更加简单!--JohnBelmonte

但那不是 Python 字典的样子 :-) 我知道 Lua 不是 Python。Sol 允许我做我想做的事情,但 Lua 限制了我。我可以以多种方式调整代码以使其在没有冲突的情况下工作,但不是我想要的方式 :-( (也许应该添加一些变体...)。这就是我在上面指出的内容,文本的倾向可能是支持统一方法 :-)。你只能在 Lua 中实现某些对象到一定程度。我想它有点与 table.n 问题重叠——表是多用途的,但有局限性?--NDT

“你只能在 Lua 中实现某些对象到一定程度。”我不同意。你遇到的问题纯粹是关于语法糖。Lua 不是一种花哨的宏语言。另一方面,它是一种强大的函数式语言。坚持使用函数式接口,事情就会顺利进行。--JohnBelmonte

“你只能在 Lua 中语法上实现某些对象到一定程度”会更准确。尽可能地模拟 Python 字典,在功能和语法上,只是一个有了一些有趣结果的练习。Lua 在使用不同的语法/接口的情况下,不会有任何问题模拟功能。这个页面的想法只是列出一些想法和局限性。--NDT


另请参阅:SampleCodePythonListsPythonDictionaries 包含类示例。
最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 10 月 23 日下午 8:08 GMT (差异)