【转载】Lua中实现类的原理
原文地址 http://wuzhiwei.net/lua_make_class/
不错,将metatable讲的很透彻,我终于懂了。
------------------------------------------------------------
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=, y=}
--初始没有元表,所以没有定义找不到的行为
--因为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({, , }, m)
local table2 = setmetatable({, , }, m) --表本来是不能执行 + 操作的,但是通过元表,我们做到了!
for k, v in pairs(table1 + table2) do
print(k, v)
end
--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的属性和方法。
-----------------------------很久以后加的评论:(class是cocos2dx的framework里面提供的一个方法,在functions.lua里面,直接传入子类和父类即可。)-------------
这篇文章非常有助于对metatable的理解。但是,我个人觉得,实现类,用metatable显的太复杂,lua中直接用class实现更清晰。
比如 我定义一个类Person
local Person = class("Person")
function Person:ctor()
self.name = "这个人很懒"
end
function Person:talk( words)
print(self.name.."说:"..words)
end
return Person
定义好以后,这样调用
import("..module.Person") -- 首先要引入Person类,看你的路径修改
local pa= Person.new()
pa.name = "张三"
pa:talk("路人甲") --张三说:我是路人甲
local pb= Person.new()
pb.name = "李四"
pb:talk("路人乙") --李四说:我是路人甲
我觉得这样比metatable更加清晰明了,你觉得呢。
【转载】Lua中实现类的原理的更多相关文章
- lua中基类和“继承机制”
基类:基类定义了所有对于派生类来说普通的属性和方法,派生类从基类继承所需的属性和方法,且在派生类中增加新的属性和方法. 继承:继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类. lu ...
- 关于boost中enable_shared_from_this类的原理分析
首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...
- 并发包中automic类的原理
提到同步,我们一般首先想到的是lock,synchronized,但java中有一套更加轻量级的同步方式即atomic类.java的并发原子包里面提供了很多可以进行原子操作的类,比如: AtomicI ...
- 【转载】【游戏开发】在Lua中实现面向对象特性——模拟类、继承、多态
[游戏开发]在Lua中实现面向对象特性——模拟类.继承.多态 阅读目录 一.简介 二.前提知识 三.Lua中实现类.继承.多态 四.总结 回到顶部 一.简介 Lua是一门非常强大.非常灵活的脚本语 ...
- 【游戏开发】在Lua中实现面向对象特性——模拟类、继承、多态
一.简介 Lua是一门非常强大.非常灵活的脚本语言,自它从发明以来,无数的游戏使用了Lua作为开发语言.但是作为一款脚本语言,Lua也有着自己的不足,那就是它本身并没有提供面向对象的特性,而游戏开发是 ...
- lua中的面向对象编程
简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 上述代码会输出tb1 ~= tb2.说明两个具有相同值得对象是两个不同的对象,同时在Lua中table是引用类型的 ...
- Cocos2d-x Lua中使用标签
游戏场景中的文字包括了静态文字和动态文字.静态文字如下图所示游戏场景中①号文字“COCOS2DX”,动态文字如图4-1所示游戏场景中的②号文字“Hello World”.静态文字一般是由美工使用Pho ...
- Lua中的面向对象编程详解
简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 复制代码代码如下: local tb1 = {a = 1, b = 2}local tb2 = {a = 1, b ...
- Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类
主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3 参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 在 ...
随机推荐
- iOS- 非ARC的项目内存管理细节详解(实战)
1.前言 接上文:iOS- 如何将非ARC的项目转换成ARC项目(实战) 2.内存管理时相关的配置 当我们把将非ARC的内存管理都管理好后,发现在做有些操作的时候内存还是在一直的缓慢增加 比如做一个最 ...
- docker配置网络
1.暂停服务,删除旧网桥#service docker stop#ip link set dev docker0 down#brctl delbr docker0 2.创建新网桥bridge0#brc ...
- C/S结构
C/S结构 编辑 同义词 C/S架构一般指C/S结构 C/S 结构,即大家熟知的客户机和服务器结构.它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Serv ...
- MYsql 数据库密码忘记(Window)-2(mysql 5.7)
很久没用Mysql了,再次打开,发现用不了了,密码忘了,服务也无法打开,在cmd中输入mysql之后,显示不是内部指令. 看来问题是mysql服务打不开了 (1)在cmd中 输入net start m ...
- html5 isPointInPath相关操作
<body> <canvas id="> </canvas> <script type="text/javascript"> ...
- Python re(正则表达式)模块
python正则表达式 正则表达式是一个特殊的字符序列,它能帮助我们方便的检查一个字符串是否与某种模式匹配.Python自1.5版本起增加了re模块,它提供Perl风格的正则表达式模式.re模块使Py ...
- JavaScript词法分析解析
函数在调用之前,会进行词法分析或者叫语法分析: 1. 函数在形成调用的那一瞬间,会有一个活动对象叫 active object ,简称AO,会分析如下几条: 形式参数 函数内局部变量声明 函数声明表达 ...
- CF#498 1006F Xor-Paths
题意:一个n * m的矩阵,求从左上走到右下经过的数异或和为k的方案数. 题解: 因为数据范围较小,所以我们可以采用meet in the middle过掉此题... 然而define inf LL ...
- BZOJ2761 不重复的数字 【treap】
2761: [JLOI2011]不重复数字 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 5517 Solved: 2087 [Submit][S ...
- 洛谷 P3521 [POI2011]ROT-Tree Rotations 解题报告
P3521 [POI2011]ROT-Tree Rotations 题意:递归给出给一棵\(n(1≤n≤200000)\)个叶子的二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少. 大体 ...