Lua学习笔记(一)

来源:互联网 发布:amd锐龙 知乎 编辑:程序博客网 时间:2024/05/18 00:25

一.先来说说什么是lua

至于什么lua 的起源啊,什么大道理一样的东西就自行百科吧
我们重点谈谈为什么要学lua
目前lua的主要应用有
  1. 游戏方面的热更新框架,由于lua属于脚本语言,说起脚本语言你可能会想到JavaScript,没错,最大的特点就是不用发布时一起拿去编译,而是在用户使用时直接渲染,对于lua来说就是直接调用,那么我们再来想想,一般我们的手机游戏是不是经常更新?更新的时候为什么只是下载几m的数据而不是要求玩家把整个游戏安装包下载下了再覆盖(当然有的时候也存在要求玩家去下载整个包,那种情况是有重大变化的更新,属于架构层面的问题,我们这里不深入谈,大部分情况都是在游戏内进行热更新)。那我们换个思路想,是不是除了lua其他语言就不行呢?当然,条条大路通罗马,只是效率不一样而已,比如我一样可以把程序拆分成很多个dll,每次检查更新dll只是这样导致整个架构混乱,而且效率低下。当然现在做热更新用的肯定不是原生的lua,不过我们学习当然要从基本学起,以后的事情以后再说。
  2. 除了热更新,其他比如演算动画,剧本,角色数据等这些经常改动的东西也可以用lua来写,你想,我们做了个3d的rpg游戏,然后里面有句台词我们不满意要改,难道还要把整个客户端都重新编译一下嘛?有点夸张,不过意思大家都懂。
  3. 游戏之外,lua可以和大部分编程语言混合使用,结合lua轻便,随性,方便扩展的特点,可以轻松做到写插件,web脚本,应用扩展等等优点。
其次,比起大部分编程语言,lua的学习成本很低,语言风格随意,而且又可以辅助其他编程语言进行开发,可以说是性价比最高的语言之一,所以不管是不是从事游戏相关行业,学习lua还是很有必要的。

二.开始lua之旅

废话这么多,让我们直接开始,先让我们搭建开发环境
lua和java一样是有专门的虚拟机的,我们称之为LVM,LVM是基于堆栈的结构,或者说整个lua语言底层非常依赖栈,基于堆栈有什么好处?稍稍了解过cil的同学肯定想到了,与cpu啊系统啊这些统统无关(废话么,本来就是虚拟机)( ̄▽ ̄)~*
关于环境安装请看里面相当详细了,基本是一站式服务,顺带还装上了ide
基本语法大部分都和c差不多吧,我这里就不废话, 不然就成话痨了,下面开始上干货

三.Lua基础语法难点,疑点

  1. lua中nil表示无,类似于null,但某变量a不用时直接a=nil即可,gc会自动回收
  2. lua中默认变量为全局变量,局部变量请加上local
  3. lua中的表类似数组和字典的结合体,既可以用数字索引a[1],也可以用字符串索引a[key],当索引不存在时,返回nil,不会报编译错误,因为根本没编译
  4. lua的表的索引是从1开始,这里与c有较大区别。
  5. 其实感觉lua中不管是变量还是函数和表,其本质都疑似指针,直接打印表print({0})出来是一串内存地址,再比如有个表a={1,2},b=a,a=nil,但是b依旧={1,2},想想堆结构和gc机制,恩,就是指针了。
  6. 对任意变量前加#,表示获取该变量的长度。相当于c语言里面的length
  7. 对于可变参数函数function(...)三点水即可,函数里面用local args={...}来接受参数,索引1~n依次接收
  8. lua不支持线程,但提供协程,协程感觉就像副线程一样,虽然可以开很多歌协程,但同一时刻只能有一个协程运行,协程可以随意挂起再启动直到协程死亡。
  9. lua里面for循环十分诡异,大部分情况直接用for v,k in pairs/ipairs do 代码end 结构,pairs和ipairs是官方给的用来迭代的函数,这样用起来有点像foreach,但你也可以自己写迭代函数,每次迭代返回的第一个参数 即这里的control,即会返回给i,也会反馈给control,当然你也可以不用这么做,等着死循环吧,state状态变量只在第一次赋值时有效(函数调用)比如我这里刻意把state写为1,按理只会执行一次循环,但结果还是正常输出。嘛不管那些歪门邪道,一般我们都用pairs和ipairs就行了,符合大众习惯。pairs和ipairs的区别在于,pairs适用于table等引用类型,ipairs适合值类型,记住i->int->ipairs就行了,当然lua没有int和float之分,lua所以实数类型是number
function square(state,control)if(control>=state) thenreturn nilelsedocontrol=control+1state=1endreturn control,control*controlendendfor i,j in square,9,0 doprint(i,j)end
执行结果:1 12 43 94 165 256 367 498 649 81
table常用函数说明table.concat(tablename)将整个table里面的数据以字符串的形式链接,返回一个字符串table.insert(tablename,index,value)将一个数据插入到表的指定位置,如果不指定位置则默认插入到末尾table.sort(tablename)对table里面的值根据索引来排序,对应的key不变,相当于把key-value整个进行排序table.remove(tablename,index)同insert,移除指定键值对,默认为最后一位

三.表与元表

表上面已经基本说完了,下面来说说什么是元表。元表是对表的一种扩充,类似于元表是骨骼,表是血肉,有了元表我们就可以对表做各种各种有趣♂的事情。
比如表a+表b,直接相加肯定会报错,但通过元表我们可以告诉lua怎么实现这两个表相加,恩,就像运算符重载(即使设置了元表,a,b必须有至少一个元表内有相关加法式子才能成立)。
元表定义其实和表是一样的metatabel={}中间可以不放东西,也可以放,绑定元表setmetatable(table,metatable)返回boolean类型表示绑定是否成功,一张元表只能绑定一张表,再进行绑定会失败。通过getmetatable(table)可以返回绑定的metatabel。
元表怎么用?主要是通过下面这些元表里面的函数

__index

当访问表中没有的索引时,原本返回nil,但我们可以加上__index()函数来修改返回值,或者__index={xxx},将index设为一张表,那么当我们在原来表里面访问不到索引时,会自动查找我们__index对应的表里面的键值对,有满足返回,不满足nil,例如
mytable = setmetatable({key1 = "value1"}, {  __index = function(mytable, key)    if key == "key2" then      return "metatablevalue"    else      return nil    end  end})print(mytable.key1,mytable.key2)
输出结果 value1metatablevalue
第一次访问,key1存在,索引不访问__index,而第二次访问不存在key2,索引触发__index

__newindex

如果index表示访问不存在的所以,那么newindex表示对不存在的索引赋值时触发。继续看列子
mymetatable = {}mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })print(mytable.key1)mytable.newkey = "新值2"print(mytable.newkey,mymetatable.newkey)mytable.key1 = "新值1"print(mytable.key1,mymetatable.key1)
输出结果value1nil新值2新值1nil
你看,如果我们不加元表,“新值2”肯定是给table了,__newindex设置后“新值”给了metatable,而不是table。

__call

这个函数可以让我们把表当函数来调用,tabel(args),不过不建议使用,毕竟容易吧自己搞晕,我就晕了好久

__tostring

这个大家应该故名思议了吧,可以修改print输出的结果,没啥好讲的

操作符重载

__add对应的运算符 '+'.__sub对应的运算符 '-'.__mul对应的运算符 '*'.__div对应的运算符 '/'.__mod对应的运算符 '%'.__unm对应的运算符 '-'.__concat对应的运算符 '..'.__eq对应的运算符 '=='.__lt对应的运算符 '<'.__le对应的运算符 '<='.

同理,不讲了,所有这些元表函数必须定义在元表里面再绑定元表即可生效,即可以接函数,也可以接表。

Lua面向对象

众所周知,lua是弱类型语言,设计之初就不是为了面向对象的,但在实际开发中,我们往往需要与很多如java,c#等强类型语言交互,没有面向对象的性质实在难以和上层语言很好的融合,所以为了防止世界被破坏,为了守护宇宙的和平,咳咳~~,为了很好的找到对象,所以各路大神们集思广益,硬是找到了一种lua面向对象的方法。
首先我们回顾下面向对象的特征。
  • 1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
  • 2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
  • 3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
  • 4)抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
我们知道,面线对象有属性和方法组成,而lua中table可以用来模拟属性,function可以模拟函数,而至于继承则可以通过metatable来模拟。
唯一有点难理解的就是,table即可表示类,也可表示实体对象,下面我们拿例子来说明
Person ={ name="icecubepill",age=99 }function Person:eat()--以后函数声明尽量用:不要用.    :表示自带参数self,print(self.name.."吃饭?)endfunction Person:new(o)local t = o or {}--如果o不为nil,则吧o赋值给返回的t,就像用对象赋值一样setmetatable( t, { __index=self }) --当访问索引不存在时,触发index访问self,注意这里的self指的是person,也就是说后面student访问不存在索引时再去查找person,也就是他的基类return tendStudent = Person:new()--是不是想起了继承,student不仅有person的属性还有自己的gradeStudent.grade=1stu1 = Student:new()--同样,stu1的既可以看成是student的实例,也可以看成是继承类,毕竟本来就不是面向对象语言,我们做到这样神似面向对象就可以了,仔细想一下上面的new函数你就会恍然大悟为什么会这样。stu1:eat()print(stu1.grade)
是不是没看懂?别急,我们一起分析,刚开始看我也晕了好久
首先person类,有两个属性,name和age,有一个方法eat,这是基类,至于要怎么创建对象呢?当然是new一个,诶,你说没有new函数,大丈夫,那就自己写一个new,咯,new出来了,不过新问题也来了,你怎么知道到底是类还是实例呢?我的答案是靠命名规范啊,好的命名规范可以帮你梳理代码逻辑,便于整理结构。

哇~终于说完了,感觉更多东西是只可意会不可言传的,大家多想一下为什么,多实践才是最好的学习方式,我也是刚刚学习lua,希望和大家一起学习,有错请指正。也许我讲的不够好,大家可以多参考参考别的文章,推荐文章


原创粉丝点击