Lua学习总结三
Posted 博士装呗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua学习总结三相关的知识,希望对你有一定的参考价值。
1.Lua中的模块
Lua学习到这里的时候我们产生一个疑问,Lua的代码是怎么写的呢?全都写在一个文件中吗?显然不可能。那样的话耦合度也太高了吧?
Lua中为我们提供了模块的功能,类似于C#中的命名空间,我们在Lua代码中引入模块,就可以调用模块中的公有变量或函数了,相当于提供了API接口。
实际上,模块的本质是一个table,只是table中放的是变量和函数,那么我们定义模块的方法就很简单了:
mymodule= --模块的本质是一个table
mymodule.num=1 --声明了一个公有变量
mymodule.str="str" --声明了一个公有变量
local a=2 --声明了私有变量
local str2="str" --声明了私有变量
function mymodule.getstr() --声明了公有函数
return mymodule.str,str2
end
local function getnum() --声明了私有函数,私有函数声明时直接写出函数名即可,
--不能像公有函数那样 模块名.函数名()来声明
--否则会运行报错
return mymodule.num,a
end
return mymodule
我们在另一个lua脚本中使用该模块:
require "mymodule" --引入模块,引号中填入的应该是文件名,而不是定义模块时的模块名
--为了减少出错,我们可以在定义模块的时候模块名和文件名同步
print(mymodule.num) --调用模块中的公有变量
print(mymodule.str) --调用模块中的公有变量
print(mymodule.a) --调用模块中的私有变量
print(mymodule.str2) --调用模块中的私有变量
print(mymodule.getstr()) --调用模块中的公有函数
print(mymodule.getnum()) --调用模块中的私有函数
运行结果:
公有变量可以正常获取,私有变量获取不到,公有函数可以正常调用,私有方法的调用直接报错。
2.Lua中的元表
Lua中引入了元表的概念,我的理解是:普通表可以根据其元表中定义的行为来对表中的元素进行处理,说白了就是元表为该普通表提供了自定义的方法扩展。
比如对于表,我们想要挨个输出表中的元素,现阶段只能使用for循环,但是我们能不能使用更简单的方法呢?而不是每次挨个输出都要经历for循环。我们学习了元表,可以自定义一个功能行为来解决这个问题。
在Lua中可以使用下面的方法来设置元表和获取元表:
tab1=1,2,3.4,5
tab2="a","b","c"
metatab1=
setmetatable(tab1,metatab1) --将metatab1表设置为tab1表的元表,返回值为tab1
getmetatable(tab1) --获取tab1表的元表,返回值为tab1的元表
我们知道普通表里面放正常的数据,那么元表里面放什么呢?答案是:元方法
__index原方法:当访问普通表中不存在的键时的处理方法:
这个方法是最常用的元方法,__index既可以是一个表,也可以是一个匿名函数,当__index为表时,我们访问了普通表中不存在的键的时候,会在__index表中查询该键,存在则返回值,不存在则返回nil:
tab1=1,2,3.4,5
tab2=
tab2[9]=10
metatab1=
__index=tab2
setmetatable(tab1,metatab1) --将metatab1表设置为tab1表的元表,返回值为tab1
getmetatable(tab1) --获取tab1表的元表,返回值为tab1的元表
print(tab1[9],tab1[10])
输出结果:
当__index为一个匿名函数时,则会按照匿名函数中处理的方式来处理问题:
tab1=1,2,3.4,5
tab2=
tab2[9]=10
metatab1=
__index=function(tab,key)
return("该键不存在对应的值")
end
setmetatable(tab1,metatab1) --将metatab1表设置为tab1表的元表,返回值为tab1
getmetatable(tab1) --获取tab1表的元表,返回值为tab1的元表
print(tab1[9],tab1[10])
输出结果:
引用菜鸟教程中的一段话:
Lua查找一个表元素时的规则,其实就是如下3个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
- 3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。
__newindex:当修改普通表中不存在的键时的处理方法:
__index是访问涉及的元方法,__newindex是赋值修改涉及的元方法,使用方面都是一样的,既可以作为匿名函数,也可以作为表。
当__newindex为匿名函数时,我们为普通表中不存在的索引元素进行赋值时会调用该匿名函数;
当__newindex为表时,我们为普通表中不存在的索引元素赋值时,会将键值对放到__newindex表中,而不会放在普通表中。
__ call:当我们把普通表当做函数来使用时的处理方法:
__call = function(tab,arg)
xxxxxxx
end
这样我们可以直接把表作为函数来使用:
tab=
tab(3)
__ tostring:当我们把普通表当做字符串来使用时的处理方法:
tab1="a","b","c"
tab2=
__tostring = function(tab)
print("把表当做字符串来使用")
return ""
end
setmetatable(tab1,tab2)
print(tab1) --print方法中的标准参数就是字符串,所以说是把表作为字符串来使用
运行结果:
注意运行结果中有一个空行,是因为__tostring元方法返回值是"",所以也会输出。该元方法必须要有字符串返回值,否则程序会报错。
Lua中内置的元方法有很多,我们在使用的时候查询即可。
3.Lua中的协同程序
协同程序的功能与Unity中的协程很相似,都是可以在程序运行的过程中控制暂停运行和启动运行。
说起协同程序,很多时候都会与线程来比较。它们的主要区别在于:一个具有多个线程的程序可以同时运行这几个线程,而协同程序则不同,任意时刻只有一个协同程序在运行,这个协同程序只有被明确要求挂起的时候才会挂起暂停运行。
协同程序的运行和挂起是由开发者来控制的,而线程的资源分配是由系统来完成的。
a.协同程序的定义:
co = coroutine.create( --定义一个名为co的协同程序
function (a,b) --定义一个匿名函数(必须是匿名函数)
print(a*b)
end
)
b.协同程序的启动和继续运行:
coroutine.resume(co,2,3) --启动该协同程序并一定要传入参数才能气功成功
resume默认会返回boolean类型的返回值,true表示启动成功,false表示启动失败。
Lua中还可以通过coroutine.wrap()方式来定义一个协同程序,两者的区别是后者可以通过 co(2,3) 的形式来启动协同程序,更加简单。
c.协同程序的暂停:
coroutine.yield()
嗯,看到了一个很亲切的词语。
d.协同程序的继续运行:
coroutine.resume()方法可以继续运行某个协同程序,只要传入协同程序的名称即可,不用再次传入其他参数。
e.协同程序的返回值:
可以通过匿名函数中的return来返回,也可以通过coroutine.yield()
中填入值来在暂停的时候返回结果。
f.协同程序的状态:
当协同程序被创建出来还没有开启的时候,协同程序的状态为:挂起,
启动之后到coroutine.yield之前,协同程序的状态为:正在运行
暂停了之后,协同程序的状态为:挂起
继续运行之后,协同程序的状态为:正在运行
运行完成之后,协同程序的状态为:死亡。
至此,该协同程序运行结束。
死亡后的协同程序不能再次启动了。
4.Lua中文件的操作
读取:
file=io.open("data.txt","r")
io.input(file)
print(io.read()) --io.read()获取文档中的一行
io.close(file)
对文件进行操作时,先获取到文件,然后打开文件,进行读取操作后,最后关闭文件。
Lua中提供了几种模式:
5.Lua中的垃圾回收
Lua中的垃圾回收机制是由系统自动完成的,系统将会间隔一定的时间来执行垃圾回收操作,没有引用了的对象将会被回收。
但是开发者也可以通过主动调用相关方法来进行一次垃圾回收操作。
tab1= "a","b","c","d"
print(collectgarbage("count"))
tab1=ni;
print(collectgarbage("count"))
collectgarbage("collect") --主动进行一次垃圾回收
print(collectgarbage("count"))
输出结果:
我们看到,当我们把一个表置空时,Lua使用的内存并不会马上减少,而在我们执行了一次垃圾回收后,所用内存减少了。
所以我们说,要释放某一个对象内存,直接将其置空即可,其实是不准确的,只有当Lua执行了垃圾回收之后,那一块内存才真真正正被释放。
我们不需要自己动手释放每一块内存区域,因为那样不方便,反而可能会拖累性能。
当我们需要释放某个对象时,直接将其置空,然后等待下一个垃圾回收的时间节点到来,Lua将其释放就可以了。
另外我认为我们最多只需要在必要的时候控制设置垃圾收集器的相关属性即可。
6.Lua中的面向对象编程
经过一段时间的学习,终于来到重中之重的阶段了。
Lua中没有类的概念,所以我们的封装和继承等面向对象的特性无法直接实现,但是我们可以通过表来模拟类的功能。
简单的面向对象:
表中可以放键值对,可以放函数。那么我们可以将键看做属性,将值看做属性值,函数的键看做方法名,函数本身看做方法体。
所以我们就有了简单的面向对象定义方法:
birds= name = "鸽子", voice="咕咕叫"
function birds.shout()
print(birds.name..birds.voice)
end
birds.shout()
我们定义了一个鸟的对象,对象包含鸟的名字,鸟的叫声和鸟叫的行为,这些都是放在一张表中的,我们直接调用它叫的函数,可以输出相应的结果:
这就可以看做是简单的封装。
但是这样声明函数的时候是存在一些问题的,比如:
birds= name = "鸽子", voice="咕咕叫"
function birds.shout()
print(birds.name..birds.voice)
end
a=birds
birds=nil
a.shout()
这样写是会报错的,因为虽然a现在持有了对原先对象的引用,但是在函数里,birds.name已经不存在了。所以这样很不方便。
因此我们使用另一种方法来定义函数:
birds= name = "鸽子", voice="咕咕叫"
function birds:shout()
print(self.name..self.voice)
end
a=birds
birds=nil
a:shout()
使用冒号来代替点来定义和调用函数,效果是一样的,只是函数内会有一个self的参数来代表当前的表,这就比较方便我们操作了。否则的话在调用函数的时候需要将表名传入才可以。
但是这样也会存在问题的,鸟的种类那么多,我要声明另一种鸟的话也要写这么一大堆吗?
想想我们C#中,定义好了一个类,我们就可以new很多个对象了,多么方便!
那么我们在Lua中怎样才能达到相同的效果呢?
birds= name = "", voice=""
function birds:shout()
print(self.name..self.voice)
end
function birds:new()
local tab=
setmetatable(tab,__index = self)
return tab
end
sparrow = birds:new()
dove=birds:new()
sparrow.name="麻雀"
sparrow.voice="啾啾叫"
dove.name="鸽子"
dove.voice="咕咕叫"
sparrow:shout()
dove:shout()
我们新添加了一个new方法来模拟实例化的功能。在new函数中,我们把birds赋给一个空表,然后返回这张表,这样一来我们就有了一个新的对象。所以我们声明了麻雀和鸽子两张表,它们彼此间是没有影响的:
这个new函数我们在C#中有一个洋气的名字叫做:构造函数!
其实说到这里,Lua中的继承已经呼之欲出了。鸟是一个基础模型,我们通过这个模型构造了鸽子和麻雀。
但是鸽子难道不是一个基础模型吗?鸽子还分信鸽、肉鸽、观赏鸽呢,它们都是咕咕叫的啊。
所以我们看信鸽,继承了鸽子,拥有咕咕叫的功能,我们再给鸽子添加一个送信的函数,那它就是信鸽了有木有?
然后我们看肉鸽,继承了鸽子,拥有咕咕叫的功能,我们再给鸽子添加一个被杀掉吃肉的函数,那它就是肉鸽了有木有?
啊,以上就是Lua的大部分知识点,学完之后开阔眼界,神清气爽!就等应用了。
文章中如有错误之处,烦请批评指正,谢谢!
以上是关于Lua学习总结三的主要内容,如果未能解决你的问题,请参考以下文章