1.lua编译环境配置
下载lua编译环境“SciTE”,直接安装,在SciTE中即可直接编译lua文件。
地址:https://code.google.com/archive/p/luaforwindows/downloads
或者使用lua studio。
2.lua基本语法
2.1全局变量和局部变量
Lua的全局变量不需要声明,给一个全局变量赋值的同时就创建了一个全局变量。没有加local声明的变量都是全局变量。
局部变量定义是需要加local关键字。
2.2 变量赋值
普通变量赋值与其他语言类似。但lua可以进行多变量同时赋值,多变量赋值的特点:
①先计算右边的值再赋值给左边
②当左右变量数不一致时,左边多,补nil,右边多,舍去。
2.3 逻辑运算符
3.lua的基本类型
Lua有8种基本类型,nil、boolean、number、string、userdata、function、thread、table。
3.1 nil
Lua中特殊的类型,它只有一个值:nil,一个全局变量没有被赋值以前默认值为nil,给全局变量赋值nil可以删除该变量。
3.2 boolean
两个取值false和true。
Ps:在判断条件中false和nil为假,其余所有值都为真。
3.3 number
Lua中不区分浮点数和整数、在保存时同一使用number。
3.4 string
①表示:可以使用“”(不能换行)或[[]](可以换行)。
②当字符串为纯数字时,可直接进行运算
③#可计算字符串的长度
④字符串连接使用..
3.5 table
由于lua中的table是一个“关联数组”,是lua中唯一的数据结构,可以代表队列、链表等各种数据结构。table在使用前必须创建。
由于lua库函数中与table相关的函数的数字索引下标为从1开始,所以建议table的数字下标也从1开始。
3.6 function
在lua中,函数被看作“第一类值”,与number、string同级,可以赋值给变量。
3.7 thread
线程拥有自己独立的栈、局部变量和指令指针。
Lua中特有的协同程序也是一种thread。
3.8 userdata
用户自定义类型,用于表示c/c++中的数据类型。
4.循环语句
4.1 while和repeat
使用和c++类似,不详细介绍。
4.2 数值型for循环
4.3 泛型for循环
泛型for循环通过一个迭代器函数遍历所有值,类似java的foreach循环。
ipairs遍历从索引1开始的顺序的元素。
pairs遍历所有元素。
5.函数与闭包
5.1 函数定义
optional:可选参数,未设置时为全局函数,为local时为局部函数
name:函数名
a1…an:函数参数,可为可变参数
body:函数体,函数要做的事情
value: 可以返回一个或多个值,返回多值时,变量接受规则和多变量赋值相同。也可以返回函数,返回函数时可能构成闭包。
5.2 闭包
闭包是lua中一个重要的概念。
首先明白几个定义
①词法定界:当函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征称为词法定结。
②upvalue:内部函数可访问的外部函数的局部变量称为upvalue。就像是只针对这个内部函数来说的静态变量。
简单的来说,一个函数和他的upvalue构成了一个闭包。
一个简单的例子:
当有多个内部函数时,它们共享upvalue:
嵌套的函数可以共享upvalue:
5.3 尾调用
如果函数f的在return处返回了一个函数g,f在返回g后不会做任何事情,那么这就是一个尾调用。Lua对于尾调用的函数f,会在调用g时将f的内容出栈,防止循环调用的爆栈。
6.迭代器
6.1 泛型for循环
泛型for的执行过程:
首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数
6.2 无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,所以在循环中我们可以利用无状态迭代器避免创建闭包,传递table话费的额外时间。
这个无状态可有有些误导,这里指的是除了状态常量和控制变量,没有其他的变量需要传递,而不是一个变量都不需要传递。
每一次迭代,迭代函数只使用状态常量和控制变量两个参数,不使用额外参数,这种迭代器就是无状态的迭代器。
比如ipairs:
6.3多状态的迭代器
除了状态常量和控制变量外需要其他状态才能获取下一个值的迭代器是多状态迭代器,这时需要闭包或一个table来传递参数。
闭包:
这里除了需要传递三个变量collection、index、count,所以需要用到闭包。
table:
7.模块
模块类似于一个封装库,可以把一些公用代码放在一个文件里,以API接口的形式在其他地方调用,有利于代码的重用和降低代码的耦合度。
模块由变量、函数、已知元素组成的table构成。把需要使用的变量、函数放在一个table中然后返回这个table就可以了。
模块通过require函数在其他程序中调用。
定义一个模块b,在a中调用:
8.元表
Lua中的每个值都有一套预定义的操作,比如数字可以相加、字符串可以相减,但是两个table,则不能直接进行相加的操作。
lua的table没有这些预定义操作,需要通过添加元表来定义这些预操作。可以理解为元表就是table的+、-、*、/、=等操作的集合,元表中的元方法就分别对应这些操作。
通过把b设置为a的元表。
一个元表中包含的元方法:
元表中的元方法都需要自己定义函数,否则nil。
①__add方法
类似于加法的二元元方法的调用过程:
1.对于二元操作符,如果第一个操作数有元表,并且元表中有所需要的字段定义,比如我们这里的__add元方法定义,那么Lua就以这个字段为元方法,而与第二个值无关。
2.对于二元操作符,如果第一个操作数有元表,但是元表中没有所需要的字段定义,比如我们这里的__add元方法定义,那么Lua就去查找第二个操作数的元表。
3.如果两个操作数都没有元表,或者都没有对应的元方法定义,Lua就引发一个错误。
②__index方法
__indexf方法调用过程:
1.访问table的一个索引时,如果table有这个索引,直接返回对应值。
2.若table没有该索引,查看table是否有元表,有则尝试调用元表的__index,否则返回nil。
3.若__index方法存在,调用__index方法,返回__index方法的返回值,若__index为一个table,则查找__index的索引,若__index中有这个索引,返回对应元素,否则返回nil。
③__newindex方法
__newindex用于更新table中的数据,调用过程如下
1.Lua解释器先判断这个table是否有元表;
2.如果有了元表,就查找元表中是否有__newindex元方法;如果没有元表,就直接添加这个索引,然后对应的赋值;
3.如果有这个__newindex元方法,Lua解释器就执行它,而不是执行赋值;
4.如果这个__newindex对应的不是一个函数,而是一个table时,Lua解释器就在这个table中执行赋值,而不是对原来的table。
Ps:如果在__newindex中又对一个table中不存在的键赋值,会发生循环调用__newindex,导致爆栈。
④__metatable方法
设置了一个元表的__metatable后,那么对使用这个元表的table进行setmetatable时会报错,使用getmetatable后会返回__metatable的值。
9.协同程序
协同程序与线程类似,具有独立的堆栈、局部变量、指令指针,同时可以使用全局变量。但是协同程序同一时间只能运行一个协同程序。
基本语法:
例子:
解释:
①第10行第一次启动co1协程,参数2,5传给co1的主函数,主函数开始运行,在第4行,输出“co-body 2 5”。
②在第5行调用yield函数,挂起这个协程,把12,25作为第10行resume的返回值返回,输出“main true 12 25”。
③在第11行重启这个协程,“lua”作为第5行yiled函数的返回值赋值给x,在第6行输出“co-body lua”
④函数在第7行结束,把“baby”作为第11行resume的返回值返回,输出“main true baby”
10.环境
Lua将所有的全局变量保存在一个叫_G的table中,这个table就是“环境”。
通过setfenv可以改变一个函数的“环境”,也就是改变针对这个函数的全局变量。
setfenv的第一个参数可以是函数名:
setfenv的第一个参数可以是当前函数在调用栈中的层数
11.面向对象程序设计
由于lua的table中可以存储变量也可以存储函数,所以可以用table来实现类这个概念,类的实例化、继承可以通过元表中的__index元方法来实现。
首先看table内函数的两种定义方法:
第二种定义完全等价于:
只是简便了一点,这个种定义方式在面向对象中十分常用。
举个例子:
会报错,因为Account = nil后,a.withDraw就不能用了。
但是如果使用:来定义和调用,用self代替Account就不会有问题。
11.1 类
类在c++中是一个模版,不是真实存在的对象,但是在lua中类被当作模版使用,但是其实是一个实例化的对象。
类就是一个真实存在的表,透过元方法__index来实例化这个类。
实现一个简单的类:
这就是lua中类的实现,调用a的balance变量和withDraw方法时,都会通过元方法__index从Account中调用。但每次实例化一个类时需要两步,比较麻烦,所以在Account中提供一个new方法来创建类,简化操作。
11.2 继承
实例化一个类的过程其实也是一次继承。
使用new的版本:
使用new的话整个程序结构更加清晰。
11.3 多重继承
继承时,是指如果子类没有这个字段,则在父类中进行查找。多重继承,是指如果子类中没有这个字段,则在继承的多个父类中进行查找。在单继承是将子类的元表的__index方法定义为父类即可,而多继承时,需要将子类的元表的__index方法定义成一个函数,来实现在多个父类中进行查找。
12.写在后面
这个笔记在介绍lua中的闭包、协同、table等比较独特的概念,主要通过几个简单的例子说明,希望可以讲清楚最基础的原理。
有些地方可能理解的不够到位,如果有问题或者错误的地方,欢迎大家指正。