Lua中类的实现原理探讨(Lua中实现类的方法)

Lua中没有类的概念,但我们可以利用Lua本身的语言特性来实现类。

下文将详细的解释在Lua中实现类的原理,涉及到的细节点将拆分出来讲,相信对Lua中实现类的理解有困难的同学将会释疑。

类是什么?

想要实现类,就要知道类到底是什么。

在我看来,类,就是一个自己定义的变量类型。它约定了一些它的属性和方法,是属性和方法的一个集合。

所有的方法都需要一个名字,即使是匿名函数实际上也有个名字。这就形成了方法名和方法函数的键值映射关系,即方法名为键,映射的值为方法函数。

比如说有一个类是人,人有一个说话的方法,那就相当于,人(Person)是一个类,说话(talk)是它的一个方法名,说话函数是它的实际说话所执行到的内容。

人也有一个属性,比如性别,性别就是一个键(sex),性别的实际值就是这个键所对应的内容。

理解了类实际上是一个键值对的集合,我们不难想到用Lua中自带的表来实现类。

实例是什么?

如果理解了类实际就是一个键值映射的表,那么我们再来理解实例是什么。

实例就是具有类的属性和方法的集合,也是一个表了。听起来好像和类差不多?

类全局只有一个集合,相当于上帝,全局只有一块内存;而实例就普通了,普天之下有那么多人,你可以叫A说一句话,A便执行了他的说话方法,但是不会影响B的说话。因为他们是实例,彼此分配着不同的内存。

说了那么多废话,其实实例就是由类创建出来的值,试着把类想象成类型而不是类。

两个语法糖

试着创建一个人类 Person

代码如下:

Person = {name="这个人很懒"}

以上代码将Person初始化为一个表,这个表拥有一个为name的键,其默认值是"这个人很懒"。

说成白话就是人类拥有一个叫名字的属性。

那就再赋予人类一个说话的功能吧。

代码如下:

Person.talk = function(self, words)
    print(self.name.."说:"..words)
end

以上代码在Person表中加入一个键值对,键为talk,值为一个函数。

好了,只要调用,Person.talk(Person, "你好"),将会打印出:这个人很懒说:你好。

不过在写程序时,大家都习惯把function放在前面,这就是函数的语法糖:

代码如下:

function Person.talk(self, words)
    print(self.name.."说:"..words)
end

这与上面的函数定义是等价的,但是这么写你就很难看出来talk其实是Person表中的一个键,其对应的值为一个函数。

当然嘴巴都是长在自己身上的,说话只能自己说,不可能自己张嘴别人说话,所以每次都传个self参数实在是有点不美观,于是冒号语法糖上场。

我们还可以这么定义人类的说话功能:

代码如下:

function Person:talk(words)
    print(self.name.."说:"..words)
end

这与上面两段代码都是等价的,它的变化是少了self的参数,将点Person.talk改为了冒号Person:talk。

但是函数体内,却依然可以使用self,在使用:代替.时,函数的参数列表的第一个参数不再是words,Lua会自动将self做为第一个参数。这个self参数代表的意思就是这个函数的实际调用者。

所以我们调用Person:talk("你好")与Person.talk(Person, "你好")是等价的,这就是冒号语法糖带来的便利。

如何查找表中的元素?

下面我们需要理解在Lua的表中是怎么查找一个键所对应的值的。

假设我们要在表p中查找talk这个键所对应的值,请看下面的流程图:

代码如下:

p中有没有talk这个键? 有 --> 返回talk对应的值
        |
       没有
        |
p中是否设置过metatable? 否 -->  返回nil
        |
        有
        |
在p的metatable中有没有__index这个键? 没有 -->  返回nil
        |
        有
        |     
在p的metatable中的__index这个键对应的表中有没有talk这个键? 没有 --> 返回nil
        |
        有,返回getmetatable(p).__index.talk

理解以上内容是本文的重点,反复阅读直至你记住了。

可以看到,由于metatable和__index这两个神奇的东西,Lua能在当前表中不存在这个键的时候找到其返回值。

下面将会讲一讲metatable这个语言特性。

对metatable的理解

metatable是什么?

metatable的中文名叫做元表。它不是一个单独的类型,元表其实就是一个表。

我们知道在Lua中表的操作是有限的,例如表不能直接相加,不能进行比较操作等等。

元表的作用就是增加和改变表的既定操作。只有设置过元表的表,才会受到元表的影响而改变自身的行为。

通过全局方法setmetatable(t, m),会将表t的元表设置为表m。通过另一个全局方法getmetatable(t)则会返回它的元表m。

注意:所有的表都可以设置元表,然而新创建的空表如果不设置,是没有元表的。

元方法

元表作为一个表,可以拥有任意类型的键值对,其真正对被设置的表的影响是Lua规定的元方法键值对。

这些键值对就是Lua所规定的键,比如前面说到的__index,__add,__concat等等。这些键名都是以双斜杠__为前缀。其对应的值则为一个函数,被称为元方法(metamethod),这些元方法定义了你想对表自定义的操作。

例如:前面所说的__index键,在Lua中它所对应的元方法执行的时机是当查找不存在于表中的键时应该做的操作。考虑以下代码:

代码如下:

--定义元表m
m = {}
--定义元表的__index的元方法
--对任何找不到的键,都会返回"undefined"
m.__index = function ( table, key )
  return "undefined"
end  
 
--表pos
pos = {x=1, y=2}
--初始没有元表,所以没有定义找不到的行为
--因为z不在pos中,所以直接返回nil
print(pos.z) -- nil
--将pos的元表设为m
setmetatable(pos, m)
--这是虽然pos里仍然找不到z,但是因为pos有元表,
--而且元表有__index属性,所以执行其对应的元方法,返回“undefined”
print(pos.z) -- undefined

pos表中本没有z这个键,通过设置pos的元表为m,并设置m的__index对应的方法,这样所有取不到的键都会返回“undefined”了。

以上我们了解到,元表的__index属性实际上是给表配备了找不到键时的行为。

注意:元表的__index属性对应的也可以为一个表。

再举个栗子,希望能够加深对元表和元方法的理解,__add键,考虑以下代码:

代码如下:

--创建元表m,其中有__add键和其定义的方法
local m = {
  __add = function(t1, t2)
    local sum = {}
    for key, value in pairs(t1) do
      sum[key] = value
    end
 
    for key, value in pairs(t2) do
      if sum[key] then
        sum[key] = sum[key] + value
      else
        sum[key] = value
      end
    end
    return sum
  end
}
 
--将table1和table2都设置为m
local table1 = setmetatable({10, 11, 12}, m)
local table2 = setmetatable({13, 14, 15}, m)
 
--表本来是不能执行 + 操作的,但是通过元表,我们做到了!
for k, v in pairs(table1 + table2) do
  print(k, v)
end
--print
--1 23
--2 25
--3 27

表本身是不能用+连起来计算的,但是通过定义元表的__add的方法,并setmetatable到希望有此操作的表上去,那些表便能进行加法操作了。

因为元表的__add属性是给表定义了使用+号时的行为。

类的实现手段

好,假设前面的内容你都没有疑问的阅读完毕话,我们开始进入正题。

请先独立思考一会,我们该怎么去实现一个Lua的类?

思考ing…

种种铺垫后,我们的类是一个表,它定义了各种属性和方法。我们的实例也是一个表,然后我们类作为一个元表设置到实例上,并设置类的__index值为自身。

例如人类:

代码如下:

--设置Person的__index为自身
Person.__index = Person  
 
--p是一个实例
local p = {}
 
--p的元表设置为Person
setmetatable(p, Person)
 
p.name = "路人甲"
 
--p本来是一个空表,没有talk这个键
--但是p有元表,并且元表的__index属性为一个表Person
--而Person里面有talk这个键,于是便执行了Person的talk函数
--默认参数self是调用者p,p的name属性为“路人甲”
p:talk("我是路人甲")
 
--于是得到输出
--路人甲说:我是路人甲

为了方便,我们给人类一个创建函数create:

代码如下:

function Person:create(name)
    local p = {}
    setmetatable(p, Person)
    p.name = name
    return p
end
 
local pa = Person:create("路人甲")
local pb = Person:create("路人乙")
pa:talk("我是路人甲") --路人甲说:我是路人甲
pb:talk("我是路人乙") --路人乙说:我是路人乙

这样我们可以很方便用Person类创建出pa和pb两个实例,这两个实例都具备Person的属性和方法。

以上便是Lua实现一个类的方法,至于类的继承,当成一次练习吧,请大家思考~

(0)

相关推荐

  • Lua中类的实现

    概述 一个类就是像是一个创建对象的模具,对于Lua这种没有类的概念的语言,为了模拟类,方法是为要创建的对象制定一个原型(prototype).这个原型相当于其他语言中的类.但是原型同时也是一种常规的对象,当其他的对象(看成是原型的实例)遇到一个未知的操作时,就会去原型中查找.因此,在Lua这种没有类的语言中,为了表示一个类,只需创建一个专用作其他对象的原型.类和原型都是一种组织对象间共享行为的方式.本文将在Lua中模拟类,相关的代码可以在我的github上直接运行.  实现 在Lua中要模拟类比

  • Lua实现类继承

    mulInherit.lua 复制代码 代码如下: --[[ author:looyer@sina.com date:2014/7/18 purpose:lua的继承演示 --]] --- base class "Object" local Object = {_t = "Object"} function Object:new(id)     local o =     {         _id = id     }     setmetatable(o, se

  • Lua中类的实现原理探讨(Lua中实现类的方法)

    Lua中没有类的概念,但我们可以利用Lua本身的语言特性来实现类. 下文将详细的解释在Lua中实现类的原理,涉及到的细节点将拆分出来讲,相信对Lua中实现类的理解有困难的同学将会释疑. 类是什么? 想要实现类,就要知道类到底是什么. 在我看来,类,就是一个自己定义的变量类型.它约定了一些它的属性和方法,是属性和方法的一个集合. 所有的方法都需要一个名字,即使是匿名函数实际上也有个名字.这就形成了方法名和方法函数的键值映射关系,即方法名为键,映射的值为方法函数. 比如说有一个类是人,人有一个说话的

  • 全面了解python中的类,对象,方法,属性

    python中一切皆为对象,所谓对象:我自己就是一个对象,我玩的电脑就是对象,坐着的椅子就是对象,家里养的小狗也是一个对象...... 我们通过描述属性(特征)和行为来描述一个对象的.比如家里的小狗,它的颜色,大小,年龄,体重等是它的属性或特征.它会汪汪叫,会摇尾巴等是它的行为. 我们在描述一个真实对象(物体)时包括两个方面: 它可以做什么(行为) 它是什么样的(属性或特征). 在python中,一个对象的特征也称为属性(attribute).它所具有的行为也称为方法(method) 结论:对象

  • PHP模拟asp中response类实现方法

    本文实例讲述了PHP模拟asp中response类的方法.分享给大家供大家参考.具体如下: 习惯了asp或是asp.net开发的人, 他们会经常用到response类,这个类用于处理客户端的响应,可以实现跳转,输出等功能. 在php中没有这个类,但是确实可以通过函数来模拟这个类. /* * 类用途: 实现类似于asp中的response功能 */ final class Response { private $headers = array(); private $output; private

  • Android中TelephonyManager类的方法实例分析

    本文实例讲述了Android中TelephonyManager类的方法.分享给大家供大家参考.具体如下: TelephonyManager类主要提供了一系列用于访问与手机通讯相关的状态和信息的get方法.其中包括手机SIM的状态和信息.电信网络的状态及手机用户的信息.在应用程序中可以使用这些get方法获取相关数据. TelephonyManager类的对象可以通过Context.getSystemService(Context.TELEPHONY_SERVICE)方法来获得,需要注意的是有些通讯

  • Java中String类使用方法总结

    一.Java中关于String类的常用方法 本文只用来自己做笔记,随便写写,方便自己理解,谢谢各位的指正.下面是摘抄慕课的一部分 1.使用 substring(beginIndex , endIndex) 进行字符串截取时,包括 beginIndex 位置的字符,不包括 endIndex 位置的字符. 2.字符串 str 中字符的索引从0开始,范围为 0 到 str.length()-1 3.使用 indexOf 进行字符或字符串查找时,如果匹配返回位置索引:如果没有匹配结果,返回 -1 4.整

  • python中class类与方法的用法实例详解

    目录 类和方法的概念和实例 1.python类:class 2.类的构造方法__init__() 3.类中方法的参数self 4.继承 5.方法重写 类的特殊属性与方法 类的私有属性 总结 因为一直不太清楚面向对象的类和方法的编程思想,所以特地补了一下python-class的知识,在这里记录和分享一下. 类和方法的概念和实例 类(Class):用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 方法:类中定义的函数. 类的构造方法__init

  • javascript中定义类的方法汇总

    JS中定义类的方式有很多种: 1.工厂方式 复制代码 代码如下: function Car(){    var ocar = new Object;    ocar.color = "blue";    ocar.doors = 4;    ocar.showColor = function(){     document.write(this.color)    };    return ocar;   }   var car1 = Car();   var car2 = Car()

  • javascript中定义类的方法详解

    JS中定义类的方式有很多种: 1.工厂方式 复制代码 代码如下: function Car(){    var ocar = new Object;    ocar.color = "blue";    ocar.doors = 4;    ocar.showColor = function(){     document.write(this.color)    };    return ocar;   }   var car1 = Car();   var car2 = Car()

  • Python面向对象编程中关于类和方法的学习笔记

    类和实例 python是一个面向对象的语言,而面向对象最重要的概念就是类和实例, 记得刚学习的时候不太理解这些概念,直到老师说了一句"物以类聚". 没错就是类, 归类 物以类聚 类其实就是把一些相同特性的事物归成一类, 比如人 class Person(object): pass 我们定义了人这个类, 但人有一些特性,比如 两个眼睛,一个嘴巴, 我们把这些添加进去 class Person(object): eyes = 2 mouth = 1 已经把人的一些信息写进去了,但是人还有名

  • 详解Javascript百度地图接口开发文档中的类和方法

    JavaScript API v2.0介绍 百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口,它能够帮助您在网站中构建功能丰富.交互性强的地图应用,包含了构建地图基本功能的各种接口,提供了诸如本地搜索.路线规划等数据服务. 该套API免费对外开放.自v1.5版本起,您需先申请密钥(ak)才可使用,接口(除发送短信功能外)无使用次数限制. JavaScript API首家支持Https,如需要申请Https服务,请您认证企业信息,成为企业认证用户后,https

随机推荐