之前都是从事3D仿真类的工作,最近想找投身游戏行业,看了看各个手游公司的要求,发现几乎都需要掌握Lua这门用于热更新的语言,其实之前第一家公司用过Lua语言,当时只是简单学习了一下,用到了一个MoonSharp的Lua与C#交互的框架,只是用作更新一个小模块的功能,学的不深,最近开始系统的学习Lua,并且研究腾讯开元的ToLua框架,为自己的游戏梦努力!所以将在这里,记录自己的学习笔记。
**
Lua面向对象
**
lua的面向对象实现很有趣,lua的基本数据类型有8个 nil ,number ,string , boolean , function , table , userdata , thread 。其中的table就是实现面向对象的重中之重 ,lua对table的一些机制设计十分巧妙,只需要熟悉这些机制 ,用简洁的代码就能实现面向对象,不得不让我佩服lua语言设计者的厉害!
原表(metatable)与元方法(meatmethod)
每个table都可以有自己的原表,但table创建新的表时并不会创建原表,即其原表为nil
t={}
print(getmetatable(t))--> nil
使用元方法getmetatable,获取其原表,打印输出,结果为nil。可以使用setmetatable的方法给表设置原表
t={}
metatable={}
setmetatable(t,metatable)
print(getmetatable(t))--> table:0x005a11f2
看到输出的结果为原表的地址。
元方法是个非常牛x的机制,我们知道c++ ,c#里有个概念叫重载预算符,就是可以自定义加减乘除的运算符方法,lua也提供了这样的功能,不过lua是靠原表元方法__add(+),__sub(-),__mul(*),__div(/)来实现的,比如我们可以通过这些方法实现两个table进行相加,即求合集的功能
Set={}
local metatable={}
function Set.new(l)
local set={}
setmetatable(set,metatable)
for _,v in ipairs(l) do
set[v]=true
end
return set
end
function Set.union(a,b)
local res=Set.new({})
for k in pairs(a) do res[k]=true end
for k in pairs(b) do res[k]=true end
return res
end
s1=Set.new{1,2,3,4,5}
s2=Set.new{7,8,9}
print(getmetatable(s1))
print(getmetatable(s2))
metatable.__add=Set.union
s3=s1+s2
for v in pairs(s3) do
print(v)
end
print(s3)
输出结果如下:
table: 0x006a19f0
table: 0x006a19f0
1
2
3
4
5
7
8
9
table: 0x006a1ce8
lua也提供了一些关系类的元方法,比如:__eq,__lt,__le,下面来介绍下最重要的一个元方法,__index
lua模拟面向对象,少不了它。
如何实现类?
首先要建立这样的概念,就是每个对象要有一个原型(prototype),当对象的实例遇到一个未知操作时,原型先会去查找这个操作,找到了就回去执行它。如果有两个对象a和b,把b作为a的原型,我们输入如下语句
```bash
setmetatable(a,{__index=b})
那么当a遇到位置操作时就会在b中查找有没有这样的操作。
熟悉面向对象我们知道,一个类包含构造函数,字段,属性,方法,那么我们用下面的代码来模拟一个类
Account={balance=0}
function Account:new(o)
o=o or {}
setmetatable(o,self)
self.__index=self
return o
end
function Account.withdraw(self,v)
self.balance=self.balance-v
end
a=Account:new{balance=10};
a.withdraw(a,1)
print(a.balance)
这段代码里我们用到了__index,它的意思就是设置表的原型索引,这里我们把Account这个table的原表设置为自己,原型索引也设置为自己这个table,分析下代码的工作流程,当我们创建了Account 的实例a,并调用withdraw这个未知操作时,a会先查询自己的原表,发现原表就是Account,那么去执行withdraw方法时,发现自身并没有withdraw这个方法,就会去查找原表里的元方法__index,然后发现Account.__index也是Account,那么就会去调用Account的withdraw方法。这样是不是就实现了类的继承,是不是十分巧妙?如果要实现重载也很简单,那就在a里再写个withdraw方法。
那么如果要实现多重继承,例如类似c#里的接口,怎么做呢?__上例里__index只是给了父类自身,要实现多重继承,我们可以将所有继承的父类存在一张table里并且给这个对象设置一个特殊的原表,__index元方法给一个函数,让其可以在父类里搜索遍历所搜父类同名方法,那么不就实现多重继承了吗?上代码:
local function search(k,plist)
for i=1,#plist do
local v=plist[i][k]
if v then return v end
end
end
function createClass(...)
local c={}
local parents={...}
setmetatable(c,{__index=function(t,k) return search(k,parents) end})
c.__index=c
function c:new(o)
local o=o or {}
setmetatable(o,c)
return o
end
return c
end
Named={}
function Named: getname()
return self.name
end
function Named: setname(n)
self.name=n
end
Account={}
NameAccount=createClass(Named,Account)
account=NameAccount:new({name="123"})
print(account:getname())
分析下这段代码,多重继承的类,它的实例创建是基于多个父类的,那么就需要一个特殊的方法createclass,而且是可变长参数,记录所有父类在parents这个table里,然后给它的__index方法做一个特殊处理,就是在parents中遍历查找基类方法。当我们创建一个继承与Named和Account的实例后,去调用getname方法时自身并没有getname方法,然后它会去找到它的原表和元方法_index,会在parents里遍历找到getname方法,然后去调用。
今天的学习笔记就记录到这~加油!