lua学习笔记整理
Lua 学习笔记整理
前言(废话):
我今年2月份开始进行笔记整理。然后我拖到4月份才整理结束。其实我学完后就再也没有用过了。很多知识点在我整理的时候都有些模糊不清了。但是我真的很少使用lua,而且我又不想自己白学一门知识。所以我在这之中又去查找资料。有些东西不一定正确毕竟我也是在学,但是代码的结果是准确的。最后整理了快七千字已经是我写得最多的文章了。
lua中的数据类型
nil 表示空数据,如同C#中的null
boolean,布尔型。只有true和false。
string,字符类型。
number,数字类型。可以说同C#的float类型,占32位。lua中没有整数类型。
table,表格类型。它是一个关联数组,下标是从1开始的。可以用表示来模拟面向对象。在lua中table有着很重要的作用。
function,函数类型。在lua中函数是第一类值,而在C#、C++或Java等语言中函数是第二类值。
userdata 表示任意存储在变量中的C数据类型。
thread 表示执行的独立线程(本质是“伪多线程”,是协程的概念),用于执行协同程序。
对于第一类值,我们可以将第一类值简单看作是可以存储函数的意思。变量就可以存储函数,调用函数时就可以用变量名调用。如果你要具体了解的话,我认为这篇文件就很不错:https://blog.csdn.net/qq_36383623/article/details/99288638。
Lua中的代码语句
if语句
1 | --单if语句 |
while语句和repeat语句
1 | while() do |
while和repeat都是循环语句。他们之间的区别就如同C#中的while和do while的区别。
for循环语句
lua中for循环语句的应用比较多。所以这里单独拿出来说明。
1 | --基本的for循环。i会累加当i>10时退出for循环。 |
关于变量
lua中变量如果不加local关键字,那么都算是全局变量。被使用local关键字的变量只能在自身范围内使用。具体示例如下:
1 | function Test() |
关于nil数据类型
nil表示空的数据。lua如果你要释放变量,那么你可以直接将变量等于nil。这样就可以当做是释放变量了。
关于string数据类型
string的定义
lua中定义字符串有三种方式:''、""和 [[]]。其中 [[]]符号是用来设置多行字符串。示例如下:
1 | s1 = '132'; |
字符串链接
lua中的字符串链接符为“ .. ”;示例如下:
1 | s1 = '123'; |
重要的字符串函数介绍
(这里仅罗列出我认为重要的字符串函数,如果想知道更多关于lua中的字符串函数额可以去其官方文档中查看。http://www.lua.org/docs.html)
1.字符串转数字类型
字符串转数字类型是使用函数:tonumber(e [, base])。其会将字符串转为number数据类型并返回。前提是这个字符串符合number数据类型的样子,否则会得到nil。示例如下:
1 | s = '123.01' |
前面说过字符串的拼接是使用符号" .. "。而很多其他的语言可以直接让两个字符串相加就进行拼接。lua中也可以对字符串进行相加。但是这并不是拼接。lua中字符串的相加是有前提的。相加的两个字符串必须符合number数据类型的样子。lua中字符串进行相加后其实返回的并不是字符串类型的数据而是number类型的数据。这是因为lua中自动对这个两个字符串都使用了tonumber函数然后再进行的相加(又称隐式转换)。其实lua中也可以number类型 + string类型返回number类型。这其实也是lua自动对这个string类型调用tonumber函数的结果。
2.其他数据类型转字符串
将某个数据类型变为string类型使用函数tostring(v)。其转换后会返回转换后的字符串。这在很多的语言中都存在。所以这里就仅提lua中基础数据类型里一些特殊的tostring。table使用tostring仅会返回table的内存地址。在tostring的隐式转换中,nil不允许隐式转换。如果让nil进行隐式转换会报错:attempt to concatenate a nil value 。如果要对nil进行字符串转换,那么要自行调用这个函数。在进行字符串连接的时候,lua都会对非字符串类型的数据进行tostring转换。
3.获取字符串长度
获取字符串长度有两种方法:#和string.len()。示例如下
1 | print(string.len("123")) --返回3 |
4.英文大小写转换
1 | print(string.upper("absd123"))--英文转大写,其他不变 |
5.字符串查找
lua中的字符串查找会返回两个数据。第一个数据是所查字符串在原字符串的启始位置,第二个数据是所查字符串在原字符串的末尾位置。示例如下:
1 | print(string.find("123456","23")) |
6.获取子字符串
1 | print(string.sub("123213",2,3)) -- 表示获取从下标2获取到下标3的子字符串 |
7.字符串拼接
虽然我们可以使用“ .. ”符号进行拼接但是有时候我们要拼接的太多会使得代码很长。所以这时候可以使用lua提供的字符串拼接函数进行拼接减少代码长度。当然字符拼接数量少时还是用” .. “比较好。
1 | s = "Attack:" .. 12 .." Defence:" .. 120 .. " Speed:" .. 12 .. " Hp:" .. 133; |
关于函数
1.函数的定义
在lua中函数可以返回多个值。这时函数可以给多个各不相同变量进行赋值。每个变量用逗号隔开。如果变量数少于函数返回个数,那么变量会按照函数返回数值的顺序进行赋值,多出来的返回值直接丢弃。如果变量数多于函数返回个数,那么多出来的变量被赋值为nil。示例如下:
1 | --无返回值的函数 |
前文说到在lua中函数是第一类值。所以lua中的函数都是有实例的。这也就意味着函数可以被赋值。当要调用函数的时候,我们可以使用变量名加“(参数)”来调用函数。示例如下:
1 | function ReturnNums() |
lua中也存在着匿名函数。所谓匿名函数就是没有函数名的函数。匿名函数的示例如下:
1 | hide = function () |
2.函数的参数简化
在lua中如果函数只有一个参数且这个参数是table和string类型的参数,那么在调用函数的时候函数的“()”符号可以省略。示例如下:
1 | function ShowString(str) |
在我个人看了,函数调用少了括号会有些不直观。所以我个人建议别这么做。这就是多打两个字符的事可以不用省略。
3.可变参的函数
在lua语言中存在可变参的函数,即参数个数可以任意给。如C#使用param作为可变参的关键字,lua中使用"..."作为可变参的关键字。示例如下:
1 | function MultiPara(...) |
如果你使用的lua版本是5.1以前的版本。那么你可以使用arg关键字来代替“{...}”,但是lua5.1版本后就不会自动帮你打包了。
可变参数除了将其转换为表用pairs函数遍历以外,你还可以用select函数对其进行遍历。之前for循环语句的说明中有说到pairs函数遇到nil会跳过,而ipairs函数会直接中断遍历。那如果我并不想跳过nil时,我们就可以使用select函数。select函数会返回从某个可变参数下标开始到整个可变参数末尾的所有数据。而select函数还提供了一个特殊的用法使得我们可以得到这个数据组的长度。具体看下面的示例:
1 | function MultiParaWithNil(...) |
4.函数闭包
(函数闭包是整个lua的函数知识点中最重要的地方,最好去看网上大佬的。而这里也是只是我的理解。)
我认为函数的闭包就是一个函数中嵌套子函数,子函数可以使用父函数的局部变量。这样就称为闭包。具体示例:
1 | function Fun() |
num是Fun()函数中的局部变量。按道理来说num应该在Fun()函数调用结束后被回收掉。而结果却是num可以加一后正常输出。如果我们多调用几次f(),那么num的数值会一直增长。这就意味着num并没有被回收或是说num被某个新的东西保存下来了(这里我确实有些不太理解。网上有人说闭包开了一个新的空间将这种值保存下来了,也有人说是没有回收。)类似于num的变量,我们称之为upvalue。
一个函数中可以有多个子函数,而这些子函数所使用的upvalue是相同的。示例如下:
1 | function FunWithChildren() |
虽然你和我结果一定不一样,但是你仍然可以发现a和b输出的表地址都是相同的。所以在一个闭包中upvalue值是共用的。
5.函数的补充
词法域:
若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为“词法域”。外部函数中的局部变量在匿名函数内既不是全局变量,也不是局部变量,将其称之为“非全局变量”。这也是闭包的定义。
模块中不允许定义local函数和字段
关于Table数据类型
表也是lua很重要的一个数据类型。因为lua是被设计为一个面向过程的语言,所以按道理来说lua并不存在面向对象的特性。可就是因为表的存在,所以我们可以用面向对象的思维去使用lua。除此之外表的作用还有很多比如作为数组、作为字典等。
1.表当做数组使用
在lua语言中表的长度不固定,也就是说表的长度可以动态进行改变。lua中表可以作为数组来使用,但lua中表的下标是从1开始的。以下给出lua中用表当数组的代码例子:
1 | tableArray = {11,12,13}; |
用表当做数组使用就算下标越界了也不会报错而是返回nil。
2.表当做字典来使用
在lua语言中table数据类型也可以拥有类似字典的功能。实例代码如下:
1 | tableDic = {dragon ="red Star"}; |
如果你要添加信息可以直接tableDic.键值 = 值;进行添加。实例如下:
1 | tableDic.hello = "world"; |
table做为字典的时候,并不会因为键值重复而产生报错。而是后面的定义的数值会覆盖掉前面的数值。实例如下:
1 | tableDic = {dragon ="red Star",dragon = "132"}; |
3.获取表长度
表长度的获取可以使用‘#’符号进行获取(lua中string类型获取长度也是用这个符号)。但是#符号在使用过程中有很多限制。在我现在的使用过程中,我也只有求数组类型表长度时才使用这个符号。这里举一个#符号求不出表长度的例子:
1 | tableDic = {dragon ="red Star"}; |
所以在操作表时,对于#符号的使用要慎重。而在lua5.2版本以下,lua官方提供了一个函数帮助我们求表长度 table.getn()。
4.表的遍历
这个内容其实在for语句介绍中有说明过了。这里再复述一下。
对于当做数组类型的表,我们遍历起来还是很轻松的。因为我们可以直接知道它的长度,并且我们还可以用下标的方式进行访问。这里无论你是使用for循环还是while循环都可以。实例代码如下:
1 | tableArray = {11,12,13}; |
除了直接使用下标遍历外,我们还可以使用lua提供的ipairs函数进行遍历。代码如下:
1 | tableArray = {1,2,3}; |
当表作为字典使用时,上面的方法就不适用了。在lua中提供了pairs 。他都会返回两个值,第一个值是字典中的key,第二个值是字典中的value。我们可以使用这两个函数对表进行遍历。实例代码如下:
1 | tableDic = {dragon = "red", Dog = "yellow", cat = "dark"}; |
pairs这个也可以遍历数组。当其遍历数组时,第一个值返回的是数组的下标。但是ipairs不能遍历字典。
补充:
这里再说明一次pairs和ipairs的区别。
1. pairs遇到空数据时会跳过继续执行,而ipairs则会立刻中断
2. pairs可以遍历把表当做数组和字典的表,但是ipairs只能遍历把表当做数组的表。
5.表做为数组一些常用的表函数
这里我仅仅是简单说明了一下他们的使用,如果你需要更多关于他们的信息,你可以去查找lua提供的官方手册。
表的链接函数concat:这个函数我一般是来用作输出数组类型的表数据。如果用在字典类型的表数据上,其只会返回空。实例代码如下:
1
2
3
4
5
6tableDic = {dragon = "red", Dog = "yellow", cat = "dark"};
str = table.concat(tableDic);
print(str); --返回空
tableArray = {1,2,3,4,5,6}
str = table.concat(tableArray);
print(str); --返回123456在默认情况下,concat函数会认为你所用链接字符为空,且长度从开头到结尾。但是这些我们都可以进行指定。代码如下:
1
2
3
4
5tableArray = {1,2,3,4,5,6}
str = table.concat(tableArray,"-"); --指定链接符号为"-"
print(str); --结果为:1-2-3-4-5-6
str = table.concat(tableArray,"-",2,5); --指定链接符号为"-",并且链接下标从2到5的数据
print(str); --指定链接符号为"-"表的插入函数insert:insert函数需要三个参数第一个是要插入的表,表的下标,要插入的数值。实例代码如下:
1
2
3tableString = {"hello","world"}
table.insert(tableString,2,"the") --要插入的表,下标,要插入的数值
print(table.concat(tableString," ")) --输出hello the world这里我们指定的下标一定要在1到数组长度加1的范围中,不然会报错。
1
2
3
4
5
6tableString = {"hello","world"}
table.insert(tableString,3,"the") --表长为2,所以3是合法数值
print(table.concat(tableString," ")) --输出hello world the
tableString = {"hello","world"}
table.insert(tableString,4,"the") --表长为2,所以4是不合法数值。报错
print(table.concat(tableString," "))表的移除函数remove:remove函数默认情况下会移除数值中最后的值。我们仍然可以指定要删除的下标。且这个范围只能在1到数值长度之间。remove函数会返回被删除的值。实例代码如下:
1
2
3
4
5
6tableString = {"hello","the","world"};
print(table.remove(tableString))--会返回被删除的值,并且删除的是最后一位。输出world。
print(table.concat(tableString," "))--输出hello the
tableString = {"hello","the","world"};
print(table.remove(tableString,2))--会返回被删除的值,并且我们指定删除"the"。输出the。
print(table.concat(tableString," "))--输出hello world表的排序函数sort:默认是升序排列,且会直接改变表中的内容。实例代码如下:
1
2
3tableSort = {10,20,1}
table.sort(tableSort)
print("sort:" .. table.concat(tableSort," "));--输出sort:1 10 20需要注意的是lua中表排序只对类似数组的表才起作用。其他都需要自己写排序函数。
6.表的自定义下标
lua中表可以自定义下标。所以你可以让下标符合你之前的想法下标从0开始。我个人认为这个作用没有多大,所以我直接给出实例。
1 | t = {[0] = 10, [1] = 1} |
其实这其中还有一些坑。实例代码如下:
1 | t = {[0] = 10, [1] = 1,2}--或者t = {[0] = 10,2, [1] = 1} 输出结果一样 |
从上面的代码结果我们可以知道,用户自定义下标的内容会被lua自身定义的下标内容所覆盖。
lua的协程
1.关于lua的协程
如果你是因Unity而想学习lua语言,那么你一定对其不陌生。但而Unity中协程是使用迭代器实现的,lua中协程却是Thread类型。但是协程就是协程,即使它的类型是Thread类型。实例代码:
1 | --先定义协程要执行的函数 |
关于lua的协程,我掌握的也不是很多。这里就简单提及使用。
2.协程创建和使用
我知道的lua中协程创建和使用的方法有两种:
1 | --先定义协程要执行的函数 |
3.补充
coroutine.resume():第一个返回值是表示是否成功执行。第二个是返回coroutine.yield(?)里?,前提是要有这个函数。而直接函数方式调用协程(如cof)的只有返回coroutine.yield(?)里?,前提是要有这个函数。
coroutine.status(cof)获得协程的状态,协程状态有三种dead(死亡),suspended(暂停|挂起),running(进行中)。coroutine.running()可以获得正在运行的协程号。
关于lua的元表
在lua中元表是一个很重要的概念。lua是一个面向过程的语言,理论上来说它不能面向对象进行开发。但是因为元表的存在lua可以模拟面对对象的性质。元表的知识点很多,这里我就简单介绍一些我认为重要的知识点。
设置元表
1
2
3metaTable = {}
objTable ={}
setmetatable(objTable,metaTable)--这里将metaTable设置为objTable的元表有时候我们也称像objTable为“子表”。这个说法并不准确,因为lua没有继承的概念。不过我们可以这么说。如果没有使用setmetatable函数,那么metaTable = {}只是单纯声明了一个表。其实无论元表还是子表本质上还是表。
元表的特殊符号(变量)
① __tostring
对于一个表当我们使用print输出的时候会得到表的内存地址。但是有时候我们想得到自己想要的信息就可以使用__tostring符号进行设置。实例如下:
1
2
3
4
5
6
7metaTable = {}
objTable ={}
setmetatable(objTable,metaTable)
metaTable.__tostring = function()
return "this is metaTable";
end
print(objTable)这里你可能发现了,我使用的是子表进行调用。如果是用元表进行调用则会输出元表的内存地址。这里我再提供一个例子:
1
2
3
4
5
6
7
8
9
10metaTable = {}
objTable ={}
setmetatable(objTable,metaTable)
metaTable.__tostring = function()
return "this is metaTable";
end
objTable.__tostring= function()
return "this is objTable";
end
print(objTable)--输出结果仍然为this is metaTable通过上面两个例子,我们可以得出一个结论:__tostring是仅针对元表的特殊符号。而这个只会被子表调用。
② __index 和 __newIndex
上面的关于表中,我们知道如果访问一个不存在表中的数据时,我们会得到nil。实际上,lua在这个表中找不到这个数据会去找这个表的元方法——__index。如果没有这个,那么返回nil。 而__index本身也是一个表。所以如果 __index 中也没有这个数据,则它会再向上找它的元表中的 __index 。最终找到为止。实例代码:
__index 无元表
1
2
3
4
5
6indexTable = {}
iTable = {}
setmetatable(iTable,indexTable)
indexTable.__index = {i = 1}--设立可以将表设置为自己——indexTable
print(iTable.i)--iTable中并没有i这个属性,所以程序去__index表中查找。表中有所以返回其数值:1。
print(iTable.j)--iTable中并没有i这个属性,所以程序去__index表中查找。表中没有所以返回其数值:nil。__index 存在元表
1
2
3
4
5
6
7
8
9
10indexTable = {}
iTable = {}
setmetatable(iTable,indexTable)
indexTable.__index = {i = 1}--设立可以将表设置为自己——indexTable
print(iTable.i)--iTable中并没有i这个属性,所以程序去__index表中查找。表中有所以返回其数值:1。
metaTable = {}
setmetatable(indexTable.__index,metaTable)
metaTable.__index = {j = 2}
--iTable中并没有i这个属性,所以程序去__index表中查找。__index表也没有则去__index的__index表中寻找。最终返回2。
print(iTable.j)~~其实这个思维也可以套到 __tostring的使用上。~~
关于元表更多的特殊符号可以去查找lua的官方文档。
lua脚本之间的相互调用
一个lua文件加载另外的lua文件使用require关键字。我们可以通过直接设定绝对路径进行文件的载入。
当然一般情况下我们都是想使用相对路径去加载。在使用require关键字进行相对加载的时候,我们还需要做一些预先的步骤。首先我们要知道lua加载模块的时候会从package.path中查找。而我们项目的路径不一定会在这个路径中,所以调包的时候最好修改package.path的内容。要么加上我们的项目的绝对路径或者是直接将路径改为项目的绝对路径。然后我们就可以使用相对路径进行调用了。特别说明一下package.path其实就是字符串类型。我们使用它就可以像直接使用字符串一样使用便好了。实例代码如下:
1 | package.path = package.path .. ";E:\\Lua\\lua studying code\\LuaCourse2\\?.lua" |
传进去的名字会和package.path中的内容一个个比对,直到它找到为止。
当我们要判断一个脚本是否被加载成功。我们可以使用package中的loaded字典就可以判断出是否被加载。如果加载成功则返回true。
1 | print(package.loaded["Test"]) |
如果我们要重新加载这个模块,那么我们必须要先把loaded字典对应的值设置为false。
lua中特殊的表——_G表
在lua中_G表(又称为大G表)是很特殊表。这个表将此lua文件中,我们声明的所有全局的变量都存储起来。但是本地变量不会被存储起来。而我们使用require得到的东西可以说是我们所请求的lua文件中的大G表。
lua中OOP模式案例
这个案例是我在学习lua的时候跟着他人敲的。这里就做一个分享吧。
1 | --封装 |
多态的演示:
1 | --多态 |