热更新语言--lua学习笔记
一.lua安装和编程环境搭建
lua语言可以在官网:http://luadist.org/下载安装包安装,编程IDE之前学习使用的是SciTE(https://www.cnblogs.com/movin2333/p/14348703.html),这个IDE安装时会一并安装lua,当然,vscode、idea、luaStudio等IDE也可以编写lua,这次使用SublimeText编写lua,SublimeText的内地官网:http://www.sublimetext.cn/。
二.lua语法:变量
1.简介:lua中的变量类似于c#种var,使用时不需要声明变量类型,赋值时确定变量类型。没有声明的变量可以直接使用,默认值为nil。
2.变量分类
1)简单变量类型 nil number string boolean
2)复杂数据类型 function table userdata thread
3.简单变量类型
1)nil:空值
2)number:数值,所有的数值类型(不管是整型还是浮点型)都是number类型
3)string:字符串,声明字符串使用单引号或者双引号都可以
4)boolean:布尔值
5)和类型相关的函数:type(xxx)--获取变量xxx的类型
4.string类型的相关函数
1)使用#获取字符串长度:
str = "双引号字符串" str2 = '单引号字符串' --获取字符串的长度 print(#str)
注意:一个汉字占用3个字节(utf-8编码)
2)字符串多行打印
--字符串多行打印 --lua中支持转义字符 print("123\n123") --定义多行字符串 s = [[你好 我的宝贝 欢迎来到 lua]] print(s)
3)字符串拼接
str = "双引号字符串" str2 = '单引号字符串' --字符串拼接,通过..拼接 print(str..str2) s1 = "123" s2 = 1232 s3 = true --print(s1..s2..s3) 报错,boolean值不能拼接 print(s1..s2)
注意:boolean值拼接时报错。
--字符串拼接,通过format函数拼接 print(string.format("你好,我今年%d岁,尚未婚配",18)) --%d:配对数字 --%a:配对字母 --%s:配对字符
4)其他类型转字符串
--非字符串类型转字符串 print(tostring(true)) --使用tostring函数,直接打印默认也会调用tostring函数(和C#其他类型转字符串方式是一致的) print(true)
5)字符串的其他函数
--其他字符串函数 s = "addDEIN" --小写转大写 print(string.upper(s)) --大写转小写 print(string.lower(s)) --反转字符串 print(string.reverse(s)) --字符串索引查找 print(string.find(s,"ddD")) --截取字符串 print(string.sub(s,3,5)) --字符串重复 print(string.rep(s,3)) --字符串修改 print(string.gsub(s,"dd","**")) --字符转ASCII码 a = string.byte("lua",2) print(a) --ASCII码转字符 print(string.char(a))
注意:lua中有多返回值,如查找函数中返回值为“ddD”字符串在s中从第2个字符到第4个字符,替换字符串中第二个返回值1代表替换了1次。lua中字符串索引是从1开始的,而不是0(和C#不同)。
三.lua语法--运算符
1.算数运算符:支持+、-、*、/、%,不支持自增++自减--复合运算符+=、-=、*=、/=、%=。注意:在lua中+号没有拼接字符串功能,使用..进行字符串拼接,将数字字符串和数字使用+号连接时系统会自动将字符串转为number类型进行加法运算。
lua中支持c#中没有的幂运算,使用^符号(如3的4次幂记为3^4),但是这个符号在c#中是幂运算符。
2.条件运算符:支持>、<、>=、<=、==、~=(不等于),注意:不等于符号和c#不同。
3.逻辑运算符:and(与)、or(或)、not(非),注意:lua中逻辑运算符和c#符号不同,但是功能几乎相同,都支持短路。
4.lua中不支持位运算和三目运算
四.lua语法--条件分支语句
1.if语句
--条件语句 a = 2 --单分支 if a < 3 then print("a<3") end --双分支 if a < 4 then print("a<4") else print("a>=4") end --多分支 if a < 5 then print("a<5") elseif a == 5 then print("a=5") else print("a>5") end
2.lua中没有switch语法和三目运算符。
五.lua语法--循环语句
--循环语句 --while循环 num = 0 while num < 5 do print(num) num = num + 1 end print("***********************") --do while循环 --注意:until后面是结束循环的条件,而c#中do while语句while后是继续循环的条件,这一点不同 num = 0 repeat print(num) num = num + 1 until num > 5 print("************************") --for循环 --默认自增1,i从1自增到大于5结束 for i = 1,5 do print(i) end --指定i自增2,i从1自增到大于5结束 for i = 1,5,2 do print(i) end
六.lua语法--函数
1.无参无返回值
--函数 --无参无返回值 --函数定义方式一 function F1() print("F1函数") end --函数定义方式二,类似C#中的委托 F2 = function() print("F2函数") end F1() F2()
注意:lua中代码从上向下执行,如果函数调用时还未定义,而调用完函数后定义函数,会报错。这一点和C#不同,因为C#会预先编译,因此函数先调用再定义是没有问题的,但是lua不会编译,不能先调用再定义。
2.有参数,不用指定参数类型
--函数 --有参数 F1 = function(a) print(a) end F1(345)
注意:如果函数定义了参数,但是不给定参数或者少给了多给了参数,都不会报错,lua会自动补空nil或者丢弃
3.有返回值
--函数 --有返回值 --函数可以任意返回多个返回值,使用逗号隔开 F1 = function() return 1,2,3,"true" end print(F1()) --使用多个变量接多返回值,变量个数和返回值个数不统一时也会补空或者丢弃返回值 a,b,c,d = F1() print(a..b..c..d)
4.lua中函数的类型是复杂数据类型function。lua不支持函数的重载,默认调用最后声明的函数(声明重载函数相当于改变了函数赋值)
5.变长参数
--函数 --变长参数 F1 = function(...) arg = {...} for i = 1,#arg do print(arg[i]) end end F1(1,2,3,4,5,6,7,1,2,12,1)
6.函数嵌套
--函数 --函数嵌套 F1 = function() return function() print(1234) end end --返回函数时可以使用一个变量来接收返回函数再调用 F2 = F1() F2() --也可以直接使用两个括号调用,第一个括号调用F1,第二个括号调用F1()返回的函数 F1()() --闭包 F3 = function(x) --x作为参数本来是一个临时变量,但是在返回函数中被调用,改变了x的生命周期,称为闭包 return function(y) return x + y end end print(F3(2)(3))
七.lua语法--表
1.概述:所有的复杂类型都是基于table(表)实现的。
2.数组及数组遍历
--数组 --定义数组,任意类型的数据都可以放到同一个表中 a = {1,2,3,"字符串",nil,true,nil} --lua中数组索引从1开始,所以索引0值为nil print(a[0]) print(a[1]) print(a[4]) --#是通用的获取长度的关键字,注意:计算长度时,从第一个nil开始的所有数据会被忽略,因此这里计算出来的数组长度为4,取数组长度时true和nil都没有计算在内 print(#a) --遍历 --通过#遍历数组是一种不可靠的遍历,这里就没有遍历到true for i = 1,#a do print(a[i]) end
3.二维数组及其遍历
--二维数组 a = {{1,2,3},{4,5,6}} --取值 print(a[1][2]) --遍历 for i = 1,#a do for j = 1,#a[i] do print(a[i][j]) end end
4.自定义索引
--自定义索引 a = {[0] = 1,2,3,[-1] = 4,5} --取值 print(a[0]) print(a[-1]) --获取长度时实际上从下标1开始获取元素个数,所以这里得到的长度是3 print(#a) --自定义索引跳过其中某些索引出现的异常情况 m = {[1] = 1,[2] = 2,[4] = 4,[6] = 6} print(#m) --实际有4个元素,索引值3和5对应的值是nil,但是实际得到的元素个数是6 n = {[1] = 1,[2] = 2,[5] = 5,[6] = 6,[9] = 9,[10] = 10} print(#n) --实际有6个元素,有两处跳过了连续两个索引3、4和7、8,但是实际得到的元素个数是2
5.迭代器遍历
--迭代器遍历 --#得到的表的长度不准确,使用#遍历不推荐使用,一般使用迭代器遍历 a = {[0] = 1,2,[-1] = 3,4,5} --ipairs遍历,仍然是从1开始遍历,也只能找到连续索引值,索引值断开会出现遍历不准确的问题 for i,k in ipairs(a) do print(i..":"..k) end print("******************") --pairs遍历,能遍历出所有值,推荐使用的遍历方法 for i,v in pairs(a) do print(i..":"..v) end print("******************") --pairs遍历,只遍历键的方法 for i in pairs(a) do print(i) end
6.使用table实现字典功能
--字典 --使用自定义索引的方式就可以自定义表 a = {["name"] = "movin",["age"] = 14,["sex"] = true,["1"] = 2} --使用类似于C#索引器的方式访问字典 print(a["name"]) print(a["age"]) print(a["sex"]) print(a["1"]) print("******************") --使用类似于成员变量的方式访问 print(a.name) print(a.age) --print(a.1)会报错,使用这种方式访问时索引不能是数字 print("******************") --修改和新增,直接赋值即可,有这个索引就是修改,没有这个索引就是新增 a["sex"] = false print(a["sex"]) print(a.sex) a["mm"] = 14 print(a["mm"]) print(a.mm) print("******************") --删除,置空即可 a["age"] = nil print(a["age"]) print("******************") for k,v in pairs(a) do print(k,v) end
7.利用表模仿类的实现
1)基本声明
--类 --lua中默认没有面向对象,但是我们可以使用表来表现类 Student = { --声明变量age age = 1, --声明变量sex sex = false, --成员函数Up Up = function() print("我成长了") end, --成员函数Study Study = function() print("我在学习") end, } --lua中类的属性和方法更类似于静态的属性和方法 print(Student.age) Student.Up() Student.Study()
2)表内的函数使用表内的其他元素
--类 Student = { age = 1, sex = false, Up = function(s) --在表中使用表内的变量和方法,不能直接调用如print(age),这样访问不到表中的变量age --可以通过表名点出调用的变量 print(Student.age) --可以将自己作为参数传递,然后调用 print(s.name) end, Study = function() print("我在学习") end, } --在表的外部为表添加属性或方法 Student.name = "movin" print(Student.name) --表的外部调用函数Up,将表自身作为参数传递 Student.Up(Student) --使用冒号可以简写,冒号可以将自身作为第一个参数传递。 Student:Up() --注意:定义函数时也可以使用冒号进行定义,使用self作为自身 function Student:Up2() print(self.sex) end Student:Up2()
8.表的一些公共操作
t1 = {{age = 1,name = "123"},{age = 2,name = "345"}} t2 = {name = "movin",sex = true} --表的插入 print(#t1,#t2) table.insert(t1,t2) --将t2插入t1最后 print(#t1,#t2) --删除指定元素 --默认remove函数移除表中最后一个值 print(t1[1]) print(t1[2]) print(t1[3]) table.remove(t1) print(t1[1]) print(t1[2]) print(t1[3]) --指定移除表中某个元素 table.remove(t1,1) print(t1[1]) print(t1[2]) print(t1[3]) --表的排序 t3 = {1,3,8,-1,0,5} --默认升序排序 table.sort(t3) for _,v in pairs(t3) do print(v) end --自定义排序规则,类似于C#中list的sort函数的使用,这里实现降序排序 table.sort(t3,function(a,b) if a>b then return true --这个返回值可以理解为a和b是否交换位置 end end) for _,v in pairs(t3) do print(v) end --表的拼接 t4 = {"123","456","789","111"} --将表中元素使用第二个参数的符号连接成一个字符串 print(table.concat(t4,","))
八.lua语法--多lua脚本执行
1.全局变量和局部变量
不适用local声明的变量称为全局变量,在整个脚本中都存在(无论在哪里声明);使用local声明的变量是局部变量,只在当前语句块(声明变量的语句块)中起作用。
--局部变量和全局变量 --全局变量 if true then a = "全局变量" end print(a) --局部变量 if true then local b = "局部变量" end print(b)
2.多脚本执行
如果想在一个lua脚本中执行另一个脚本或者调用另一个脚本中定义的函数等,需要使用require关键字引入其他脚本
--被引用的脚本 A = "全局变量A" local B = "局部变量B"
--引用其他脚本 --同文件夹下直接引用脚本文件名即可,注意:同一个脚本require引入两次及以上,并不会执行两次,也就是说require已经引用的脚本是无效的 require("BeenRequired") print(A) print(B) --B是局部变量,这个脚本中看不到 --使用load方法获取一个boolean值,代表脚本是否执行过 print(package.loaded["BeenRequired"]) --卸载脚本,直接把loaded的值置为nil即可 package.loaded["BeenRequired"] = nil print(package.loaded["BeenRequired"])
3.大G表
大G表是一个总表,将我们声明的所有全局的变量都存储在里面(包括大G表自身)。大G表的表名是_G。使用local声明的本地变量没有存储在大G表中。只要执行过的全局变量都存储在大G表中,因此即使脚本被卸载了,仍然可以访问脚本中声明过的全局变量。
注意:脚本也可以当成是一个函数,在脚本最后可以返回外部希望获取的内容,在外部使用require引入后可以接收这个返回值。
九.lua语法--特殊用法
1.多变量赋值
和函数的多返回值类似,多变量赋值会自动补空或者丢弃。
--多变量赋值 a,b,c = 1,2 d,e,f = 3,4,5,6 print(a) print(b) print(c) print(d) print(e) print(f)
2.函数的多返回值:函数部分已经有介绍
3.逻辑与和逻辑或
and和or不仅可以连接boolean,任何东西都可以连接,当连接的不是boolean值时,将这些值当成boolean值处理(在lua中,非boolean值当作boolean值处理的规则是nil当作false,其他都当作true处理)。
注意:lua中不支持三目运算符,但是可以使用and和or模拟三目运算符。
--使用and和or模拟三目运算符的实现 local x = 3 local y = 2 --执行逻辑:利用and和or的短路运算特性 --如果x>y运算的结果为true,则根据and的短路规则会接着判断x的值是真还是假,显然x不为nil当作true处理,因此(x>y) and x的结果是true,根据or的短路特性,不会继续判断y,因此最后返回x --如果x>y运算的结果为false,则根据and的短路特性,不会判断x,(x>y) and x的结果为false,根据or的短路特性,会继续判断y,显然y不为nil当作true处理,因此false or y的最终返回结果为y local result = (x>y) and x or y print(result)
十.lua语法--协程
1.协程的创建
--创建协程 --首先声明一个函数作为协程函数 function fun() print(123) end --创建方法一:使用coroutine表中的create函数创建协程 co = coroutine.create(fun) print(co) print(type(co)) --创建方法二:使用coroutine表中的wrap函数创建协程 co2 = coroutine.wrap(fun) print(co2) print(type(co2)) --两种方法创建的协程返回值不同,开启方式也不同 print("*********************") --通过create函数创建的协程的执行方法 coroutine.resume(co) --通过wrap方法创建的协程的执行方法 co2() --wrap方法创建协程返回值为函数类型,直接调用函数即可
2.协程的挂起
--挂起协程 function fun() local i = 1 while true do print(i) i = i + 1 --使用coroutine表中的yield函数挂起协程,create函数创建的协程yield的返回值第一个为启动协程是否成功的boolean值,yield函数传入的参数是协程这一次挂起的其他返回值,wrap方式创建的协程没有第一个默认的boolean返回值 coroutine.yield() end end co = coroutine.create(fun) --启动协程 coroutine.resume(co) --打印1 --再次启动协程,继续执行这个协程 coroutine.resume(co) --打印2
3.协程的运行状态
--协程的状态 function fun() local i = 1 while i<2 do print(i) i = i + 1 print(coroutine.status(co)) --running运行状态 --获取正在运行的协程的线程号 print(coroutine.running()) coroutine.yield() end end co = coroutine.create(fun) --使用coroutine表中的status方法获取协程的运行状态 print(coroutine.status(co)) --suspended暂停状态 coroutine.resume(co) print(coroutine.status(co)) coroutine.resume(co) print(coroutine.status(co)) --dead协程结束状态
十一.lua语法--元表
1.元表的概念和设置
1)任何表变量都可以作为另一个表变量的元表
2)任何表变量都可以由自己的元表(可以理解为父表)
3)当表进行一些特定操作时,会执行元表中的内容
--元表 meta = {} myTable = {} --设置元表 setmetatable(myTable,meta) --将meta表设置为myTable的元表
2.特定操作
1)__tostring
--__tostring meta = { __tostring = function(t) return t.name end, } myTable = { name = "movinToString" } setmetatable(myTable,meta) --当子表被当成字符串使用时,会调用_tostring方法,这个方法可以设置一个参数,系统会默认将子表自身传入 print(myTable)
2)__call
--__call meta = { __call = function(c) print("movinCall",c.name) end } myTable = { name = "movin" } setmetatable(myTable,meta) --当子表被当作函数使用时,会调用元表中的__call函数,同样的,__call的第一个参数默认为子表自身,不需要传递 myTable()
3)运算符重载
--运算符重载 meta = { __add = function(t1,t2) return t1.number + t2.number end } myTable = { number = 1 } myTable2 = { number = 2 } setmetatable(myTable,meta) setmetatable(myTable2,meta) --当两个子表进行运算符操作时,如果这两个子表有同一个元表且元表中实现了响应的运算符重载,会调用这个运算符重载,默认将这两个子表作为两个参数传递(运算符重载一定是两个参数) print(myTable2+myTable) --其他运算符重载 --__sub(减),__mul(乘),__div(除),__mod(取余),__pow(幂),__eq(相等),__lt(小于),__le(小于等于),__concat(连接符号..) --注意:没有大于和大于等于的运算符重载
4)__index和__newIndex
--__index和__newindex meta = { age = 2 } meta.__index = meta myTable = {} setmetatable(myTable,meta) --当调用表中没有的元素时,会自动在元表的__index指向的表中寻找 print(myTable.age) meta.__newindex = meta --当设置表中没有的属性时,会自动设置属性到元表的__newindex指向的表中 myTable.name = "movin" print(myTable.name) print("****************") for k,v in pairs(myTable) do print(k,v) end print("***************") for k,v in pairs(meta) do print(k,v) end
注意:为元表中的__index和__newindex赋值时最好在表的外部设置,因为__index在表的内部设置指向元表自身时这个设置是无效的。
3.元表的一些方法
--元表的相关方法 meta = {} meta.age = 1 meta.__index = meta meta.__newindex = meta mytable = {} setmetatable(mytable,meta) --使用getmetatable获取表的元表 print(getmetatable(mytable)) --使用rawget方法在获取变量时忽略元表(不去__index对应的表中找,相当于忽略__index这个变量) print(rawget(mytable,"age")) --使用rawset方法在设置变量时忽略元表(不去__newindex对应的表中设置,相当于忽略__newindex这个变量) rawset(mytable,"age",2) print(mytable.age) print(meta.age)
十二.lua实现面向对象编程
1.封装
--封装 --尝试封装万物之父object Object = {} Object.id = 1 --new方法,新建一个对象 function Object:new() --声明一个空表 local object = {} --设置元表 setmetatable(object,self) --元表的__index指向自身,调用这个表都会调用元表的方法 self.__index = self return object end local obj = Object:new() print(obj) print(obj.id)
2.继承
--继承 Object = {} function Object:new() local object = {} setmetatable(object,self) self.__index = self return object end --提供一个继承的方法 function Object:subClass(className) --使用_G总表,可以创建指定名称的表 _G[className] = {} --使用元表模拟继承 --取出新创建的表 local obj = _G[className] --设置元表 setmetatable(obj,self) self.__index = self end --创建新表Person Object:subClass("Person")
3.多态
--继承 Object = {} function Object:new() local object = {} setmetatable(object,self) self.__index = self return object end function Object:subClass(className) _G[className] = {} local obj = _G[className] setmetatable(obj,self) self.__index = self --创建base属性,实现继承后子类调用父类方法 self.base = self end --Object中提供move方法 function Object:Move() print("move") end --创建新表Person Object:subClass("Person") --重写父类Object的Move方法 function Person:Move() --保留父类逻辑,必须使用点调用,不能使用冒号调用,然后将调用者自身作为参数传入 self.base.Move(self) print("PersonMove") end --创建对象 p = Person:new() --调用Move方法 p:Move()
十三.lua自带库
1.时间相关
--常用自带库 --时间相关 --系统时间,毫秒值 print(os.time()) --自己传入参数得到时间 print(os.time({year = 2021,month = 4,day = 1})) --得到现在的时刻,将现在的年月日时分秒等存储在一个表中 local nowtime = os.date("*t") for k,v in pairs(nowtime) do print(k,v) end
2.数学运算
--常用自带库 --数学运算 --绝对值 print(math.abs(-11)) --弧度转角度 print(math.deg(math.pi)) --三角函数,参数是弧度值 print(math.sin(math.pi)) --向下或向上取整 print(math.floor(4.3)) print(math.ceil(2.3)) --最值 print(math.max(2,3)) print(math.min(4,2)) --将数字的小数部分和整数部分分离 print(math.modf(3.4)) --幂运算 print(math.pow(3,2)) --随机数,必须先设置随机数种子 math.randomseed(os.time()) print(math.random(100)) print(math.random(100)) --开方 print(math.sqrt(121))
3.路径
--常用自带库 --路径 --lua脚本加载路径 print(package.path)
PS:可以自己查看_G表中的内容,自带库都存储在_G表中,可以遍历响应的表格自学。
十四.lua的垃圾回收机制
lua中存在自动垃圾回收机制,但是在unity中使用lua时尽量手动回收垃圾,减少性能开销。
--垃圾回收 --得到当前lua占用内存数,单位为千字节 print(collectgarbage("count")) --进行垃圾回收的命令 collectgarbage("collect") --再次查看lua占用内存数,可以看到占用变小print(collectgarbage("count")) print(collectgarbage("count"))