一、Lua前言
游戏开发(VR-虚拟现实,AR-增强现实)应用场景:王者荣耀、12306
二、开发环境搭建
环境安装Luaforwindows:
IDE(集成开发环境)选择
大部分公司使用的是VSCode来搭建,其中IDEA搭建的会比较复杂。
三、Lua语法
1.输出,单行注释、多行注释
--打印语句
print("helloworld")
--单行注释两条横杠
--多行注释三种情况:
--[[
多行注释第一种
]]
--[[
多行注释第二种
--]]
--[[
多行注释第三种
]]--
注意:lua文件命名不能是中文名,并且后缀名是lua
2.变量
--lua当中的简单变量类型有
--nil number string boolean
--lua中的所有变量的声明,都不需要声明变量类型,它会自动的判断类型
--类似c#里面的var,其中lua中的一个变量,可以随便赋值,自动识别类型
--nil有点类型c#的null
a = nil
print(a)
--number类型里面,所有的数值都是number,没有整型和浮点型区分
a=1
a=1.8
--string类型里面使用单引号或者双引号包裹起来,lua里面没有char类型
a='123'
a="123"
print(a)
a=true
a=false
print(a)
--获取变量类型 使用type函数
print(type(a))
print(type(type(a))) --type返回的类型的类型为string
--lua中使用没有声明的变量不会报错,默认是nil
print(b)
--复杂数据类型
--函数:function
--表table
--数据结构 userdate
--协同程序 thread(线程)
3.字符串操作
--获取字符串长度
--一个汉字占3个长度
s="嘿嘿"
print(#s)--6
--英文字符占1个长度
a="hello"
print(#a)--5
--lua中也是支持转义字符
print("123\n123")
--字符串多行打印
str=[[
1号
2号
3号
]]
--字符串拼接,使用两个点..进行拼接
print("123".."456")
--两个变量值拼接
s1="456"
s2=123
print(s1..s2)
--使用format进行拼接
print(string.format("我今年%d岁了",18))
--占位符介绍
--%d:与数字拼接
--%a:与任何字符拼接
--%s:与字符配对
--数据类型转化
isShow=true
print(tostring(isShow))
--字符串提供的公共方法(大部分字符串方法对本身字符串不改变)
print("-------------------------")
str = "abCdefCd"
--小写转大写的方法
print(string.upper(str))--ABCDEFCD
--大写转小写
print(string.lower(str))--abcdefcd
--翻转字符串
print(string.reverse(str))--dCfedCba
--字符串索引查找(返回的是第一次出现的字符串开始位置和结束位置)
--参数1:表示的是需要查找的字符串名字,参数2:表示的是目标字符串
print(string.find(str,"Cd"))--3 4
--字符串重复
--参数1:需要重复哪个字符串 参数2:需要重复的次数(用的比较少)
print(string.rep(str,2))--abCdefCdabCdefCd
--字符串截取(注意点:在lua中,索引是从1开始)
print(string.sub(str,3))--CdefCd
--lua截取字符串,开始位置和结束位置都包含,是[]关系
print(string.sub(str,3,6))--Cdef
--字符串修改
--参数1:需要修改的字符串是哪个
--参数2:需要修改的字串
--参数3:替换的新字符串是什么
--返回值1:返回替换后的新字符串
--返回值2:返回替换的次数有几次
print(string.gsub(str,"Cd","**")) --ab**ef** 2
--字符转ASCII码
a = string.byte("Lua",1)
print(a) --76
--ASCII码转字符
print(string.char(a)) --L
特别要注意的是,跟其他语言不同,lua索引是从1开始。
4.运算符
--算术运算符: + -* / % ^
--注意:在lua中,没有自增自减运算符 ++ --;也没有复合运算符 += -= /= *= %=
--字符串在进行运算的时候,可以转化为统一类型的话,会自动转化为相同类型进行运算你
print("123"+1) --124
--print(true+1) 如果转化不了类型,就会报错
print("减法运算:"..128-2)
--幂运算 ^
print("幂运算:"..2^2) --
--条件运算符 > < >= <= == ~=(不等于)
print(1~=1) --false
print(3=="3") --false 可能是ASCII码值不一样
--逻辑运算符(也支持短路)
-- and or not
print(true and false) --false
print(true or false) --true
print(not true) --false
print(true and print("123")) --nil
--位运算符(& | 不支持运算符 需要我们自己去实现--后续有第三方库)
--三目运算符(? : 也不支持三目运算符)
5.条件分支语句
--条件分支语句
--注意:lua中是不支持Switch语句的
a=9
--if 条件 then...end
--注意点:每次写完注意些{也就是then,但是end并不是和then成双成对,end只要最后一个即可
if a > 5 then
print("值大于5")
end
--双分支
if a > 5 then
print("值大于5")
else
print("值小于5")
end
--多分支
if a < 5 then
print("小于5")
elseif a==9 then --elseif必须是连在一起的,中间不能有空格
print("等于9这个值")
elseif a==7 then --elseif必须是连在一起的,中间不能有空格
print("等于7这个值")
elseif a==6 then --elseif必须是连在一起的,中间不能有空格
print("等于9这个值")
else
print("值d大于5")
end
if a>3 and a<=9 then
print("值在3到9之间") --值在3到9之间
end
注意:lua中是不支持Switch语句的,并且在条件分支语句中,每写完一个判断条件的话后面要跟上then,写完最后的时候报上end,then可以多个,但是end只有一个,这个是特别要注意的地方
6.循环
--循环语句 while repeat...until for
print("-------while-------")
num = 0
while(num<5) do
print(num)
num=num+1
end
print("-------repeat until")
repeat
print(num)
num = num+1
until num>5 --满足条件就跳出,结束条件
print("-----------for-------")
for i =1,5 do--默认递增,i会默认+1
print(i)
end
print("-----------------")
for i =1,5,2 do --如果要自定义增量,直接逗号后面写
print(i)
end
print("----------")
for i =5,1,-2 do --如果要自定义增量,直接逗号后面写
print(i)
end
7.函数(重点)变长、嵌套闭包、多返回值
--函数的定义、调用
--function 函数名()
--end
--F1() 在函数声明前调用函数是错误的,因为在lua语言中,是从上往下执行的,
--不像c#,它是会全部编译后根据主函数进行执行对应的逻辑
--无参函数
function F1()
print("F1函数")
end
F2 = function()
print("F2函数")
end
--函数的调用
F1()
F2()
--有参函数
function F3(a,b)
return a+b
end
sum = F3(1,10)
print("两个数和为:"..sum)
print("---------------------函数传参-----------")
function F4(a,b)
print(a)
end
F4() --虽然函数有形参,调用函数的实参也可以不用传,
--如果你传入的参数和函数参数个数不匹配的的话不会报错(实参少,行参多,就会补空nil;实参多,形参少,多了的形参就会被丢弃)
F4(4,5,6)--4 只会选取形参的第一个接受
print("------------------------")
--有参函数的多个返回值
function F5(a)
return a,"123",true
end
temp1 = F5(1)
temp1,temp2,temp3 = F5(1)
temp1,temp2,temp3,temp4 = F5(1)
--多返回值时候,在前面声明多个变量来接受返回值
--如果接受变量不够,不影响运行,值接取对应的位置返回值,其他的不返回
--如果接受变量多了,不影响运行,直接返回nil
print(temp1)--1
print(temp2)--123
print(temp3)--true
print(temp4) --nil
--函数的类型
F6 = function()
print("helo1")
end
print(type(F6))
print("------变长参数-----")
--在Lua语言中函数是不支持重载的,在c#里面是函数名相同,参数个数或者参数类型不同来定义重载
--而在lua语言中,你默认调用的其实是最后声明的那个函数,本身形参什么类型都支持,也区分不了变量类型
--变长参数
function F7( ... )
--变长参数使用一个表存起来,在使用(遍历)
arg = {...}
for i = 1,#arg do
print(arg[i])
end
end
--变长参数实参可以传入多种数据类型,他都可以自动的去识别和接收
F7(1,"true",false,"123",4,5,6)
print("-----------函数嵌套---------")
function F8()
return function()
print("我可以在函数中返回函数")
end
end
F9 = F8()
F9() --我可以在函数中返回函数
--面试题:在lua里面,闭包的体现是什么
--答:在函数里面返回一个函数,然后会改变这个函数里面的变量的生命周期
--里面的函数用外面函数传进来的参数或者用外面函数声明的参数
--函数的闭包
function F10(x)
--改变传入函数的生命周期
return function (y)
return x+y
end
end
--lua是从里到外执行,c#是从外层到里面。在lua里面间接的改变的函数的生命周期
F11=F10(10)
print(F11(5)) --15
面试题:lua中的闭包的体现是什么
Lua函数可以被当成参数传递,也可以被当成结果返回,在函数体中仍然可以定义内嵌函数。lua闭包是Lua函数生成的数据对象。每个闭包可以有一个upvalue值,或者多个闭包共享一个upvalue数值。
**1、upvalue ** 如果函数f2定义在函数f1中,那么f2为f1的内嵌函数,f1为f2的外包函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也一定是f2的外包。
内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的外部局部变量(或者upvalue)
function f1(n)
-- 函数参数也是局部变量
local function f2()
print(n) -- 引用外包函数的局部变量
end
return f2
end
g1 = f1(1979)
g1() -- 打印出1979
g2 = f1(500)
g2() -- 打印出500
当执行完g1 = f1(1979)后,局部变量n的生命本该结束,但因为它已经成了内嵌函数f2的upvalue,f2又被赋给了变量g1,所以它仍然能以某种形式继续“存活”下来,从而令g1()打印出正确的值。
** 2、闭包**
Lua编译一个函数时,其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的函数时,它就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(用来查找全局变量的表)的引用以及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。 g1和g2的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而这两个闭包有各自的upvalue值。
使用upvalue代码如下:
function f1(n)
local function f2()
print(n)
end
n = n + 10
return f2
end
g1 = f1(1979)
g1() -- 打印出1989
g1()打印出来的是1989,原因是打印的是upvalue的值。
upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上的,所以只要upvalue还没有离开自己的作用域,它就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue的引用来访问它们,一旦upvalue即将离开自己的作用域,在从堆栈上消除之前,闭包就会为它分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue。当执行到f1(1979)的n = n + 10时,闭包已经创建了,但是变量n并没有离开作用域,所以闭包仍然引用堆栈上的n,当return f2完成时,n即将结束生命,此时闭包便将变量n(已经是1989了)复制到自己管理的空间中以便将来访问。
3、upvalue和闭包数据共享
upvalue还可以为闭包之间提供一种数据共享的机制。
3.1:## 单重内嵌函数的闭包 (函数创建的闭包)
一个函数创建的闭包共享一份upvalue。
function Create(n)
local function foo1()
print(n)
end
local function foo2()
n = n + 10
end
return foo1,foo2
end
f1,f2 = Create(1979)--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
f2()
f1() -- 打印1999
f1,f2这两个闭包的原型分别是Create中的内嵌函数foo1和foo2,而foo1和foo2引用的upvalue是同一个,即Create的局部变量n。执行完Create调用后,闭包会把堆栈上n的值复制出来,那么是否f1和f2就分别拥有一个n的拷贝呢?其实不然,当Lua发现两个闭包的upvalue指向的是当前堆栈上的相同变量时,会聪明地只生成一个拷贝,然后让这两个闭包共享该拷贝,
这样任一个闭包对该upvalue进行修改都会被另一个探知。上述例子很清楚地说明了这点:每次调用f2都将upvalue的值增加了10,随后f1将更新后的值打印出来。upvalue的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。
多重内嵌函数的闭包 (闭包创建的闭包)
同一闭包创建的其他的闭包共享一份upvalue。
内层闭包在创建之时其需要的变量就已经不在堆栈上,而是引用更外层外包函数的局部变量(实际上是upvalue)。
function Test(n)
local function foo()
local function inner1()
print(n)
end
local function inner2()
n = n + 10
end
return inner1,inner2
end
return foo
end
t = Test(1979)--创建闭包(共享一份upvalue)
f1,f2 = t()--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
g1,g2 = t()
g1() -- 打印1989
g2()
g1() -- 打印1999
f1() -- 打印1999
执行完t = Test(1979)后,Test的局部变量n就结束生命周期了,所以当f1,f2这两个闭包被创建时堆栈上根本找不到变量n。Test函数的局部变量n不仅是foo的upvalue,也是inner1和inner2的upvalue。t = Test(1979)之后,闭包t 已经把n保存为upvalue,之后f1、f2如果在当前堆栈上找不到变量n就会自动到它们的外包闭包(这里是t的)的upvalue引用数组中去找.
g1和g2与f1和f2共享同一个upvalue。因为g1和g2与f1和f2都是同一个闭包t 创建的,所以它们引用的upvalue (变量n)实际也是同一个变量,而它们的upvalue引用都会指向同一个地方。
8.表(table)
--表的定义
a={1,2,3,4,"1234",true,nil}
--lua中,索引从1开始
print(a[1])
print(a[5])
--#是通用获取长度的关键字
--在打印长度的实惠,空会被忽略,并且如果表中数组中某一位变成了nil,会影响#获取的长度
print(#a) --6
--数组的遍历
for i = 1,#a do
print(a[i]) --1,2,3,4,"1234",true
end
print("---------二维数组--------")
a={{1,2,3},{4,5,6}}
print(a[1][1]) --1
print("------------二维数组的遍历---------")
for i=1,#a do
b = a[i]
for j=1,#b do
print(b[j])
end
end
--自定义索引太坑,不建议自身自定义索引,很恶心!!
9.表的迭代器遍历
--迭代器遍历,主要是用来遍历表的
--#得到长度,其实并不准确,一般不要用#来遍历表
a={[0]=1,2,[-1]=3,4,5,[5]=6}
--ipairs迭代器遍历表
for i,k in ipairs(a) do
print("ipair遍历表键:"..i..",值:"..k)
end
--[[ipair遍历表键:1,值:2
ipair遍历表键:2,值:4
ipair遍历表键:3,值:5]]
--总结:由此我们总结到ipairs遍历还是从1开始遍历的,小于等于0的值得不到
--只能找到连续的索引的键,如果中间断序了,它也无法遍历出后面的内容(如果【5】=6改成【4】=6就可以遍历出来)
print("--------------------")
--pairs迭代器遍历
for i,k in pairs(a) do
print("ipair遍历表键:"..i..",值:"..k)
end
--总结:pairs遍历它能够把所有的键都找到,通过键可以得到值
--但是它遍历的索引可能会比其他语言遍历的顺序不一样(无序的)
--它会先遍历出有序的索引对应的键值,然后再遍历一开始自定义的索引的键值以及断序的键值
--相同的分别遍历表的键和值是跟上面的方法一样的,只需要一个参数接受对应的键或值即可
面试题:
ipairs迭代器和ipair迭代器的区别在哪?
1.ipairs
不能找到0和0以下的自定义索引的内容,如果是从1开始,索引顺序断了,后面的内容也就找不到了
2.pairs
在实际项目开发中,建议使用它来遍历各种不规则的表,因为它可以得到所有的信息
实操:
for i,k in pairs(表) do
end
10.字典的声明和创建
--字典
--字典的声明(字典是由键值对构成的)
a={["name"]="小黑",["age"]=14,["true"]=1,["1"]=5}
--访问变量,用中括号填键,来访问
print(a["name"])--小黑
print(a["1"])--5
--也可以通过点变量名点.键的方式
print(a.name)--小黑
--但是要注意的是,但是点的不能是数字(必须满足变量命名规范,也不是关键字),如下方式是错误的
--print(a.1)
--字典元素的修改
a["name"] = "小红"
a["true"] = 0
print(a.name)--小红
print(a["true"])--0
--字典元素的增加
a["sex"] = "男"
print(a["sex"]) --男
--字典的删除
a["sex"] = nil
print(a["sex"]) --nil 赋值nil内存对应存储的变量就会被垃圾回收了
--字典的遍历
for k,v in pairs(a) do
print(k,v)--可以传多个参数,一样可以打印出来
end
--[[
name 小红
age 14
true 0
]]
print("-----------------------")
--只遍历键
for k in pairs(a) do
print(k)--输出键
print(a[k])--输出键对应的值
end
--[[
1
5
name
小红
age
14
true
0
]]
print("--------------")
--还有一种遍历方式(删除的字典元素是不会遍历出来的)
for _,v in pairs(a) do
print(_,v)
end
--[[
1 5
name 小红
age 14
true 0
]]
11.类
代码测试:
print("-------------类和结构体----------")
--成员变量,成员函数。。。
Student={
name="小黑黑",
age=18,
sex=true,
Eat =function ()
--print(age) 这样写,这个age和表的age没有任何的关系,它是一个全局变量
--第一种方式:还是需要通过类名.的方法来访问
print(Student.name)
print("我在类中吃饭")
end,
Learn = function(t)
print("我在类中学习:")
end
}
--第二种方式:通过调用方法,形参中间接把值传过去
--Student.Learn(Student)
--C#要使用类,实例化对象new 静态直接点
--Lua中类的表现,更像是一个类中有很多静态变量和函数
--也可以在声明表过后,在表外去声明表的变量和方法
Student.grade = 99
Student.Speak = function()
print("说话")
end
--冒号可以用来声明函数,只能是function +名字的形式声明
function Student:Spead()
--lua中 有一个关键字self表示 会默认传入第一个参数
print(self.name..":马拉松倒计时4天,跑起来")
end
--访问类的成员变量和函数
print(Student.name) --小黑黑
print(Student.grade)--99
Student.Speak() --说话
Student.Learn()--我在类中学习
Student["Eat"]()--我在类中吃饭
print("------------------面试题---------讲解")
--在lua中,.和冒号的区别:
Student.Spead(Student)--马拉松倒计时4天,跑起来
--冒号调用方法,会默认把调用者作为第一个参数传入方法中
--而点的方式是需要我们将类传递进去才可以进行访问
Student:Spead()--马拉松倒计时4天,跑起来
面试题:
** 类中点和冒号的区别是什么,以及self作用,它跟this是一样吗?**
12.表的操作
print("-----------------表的公共操作-------------")
t1={{age=1,name="小红"},{age=18,name="嘿嘿"}}
t2={name="小兰",sex=true}
--表的插入
print(#t1)--2
table.insert(t1,t2)
print(#t1)--3
--表的元素访问
print(t1[3].sex)--true
--表的删除元素--传表进去,默认会移除最后一个索引的内容
table.remove(t1)
print(#t1) --2
print(t1[3])--nil
--表的删除指定元素
--第一个参数就是要移除内容的表
--第二个餐宿就是要移除内容的索引
table.remove(t1,1)
print(t1[1].name)
print("-----------------表的升序排序--------------")
--表的排序
s ={5,4,1,9,6,10,54,47}
table.sort(s)--掺入要排序的表,默认是升序
for _,v in pairs(s) do
print(v)
end
print("-----------------表的降序排序--------------")
table.sort(s,function(a,b)
if a>b then
return true
end
end)--掺入要排序的表,默认是升序
for _,i in pairs(s) do
print(i)
end
print("--------------拼接------------")
tb = {"123","456","789",12}
--连接函数,用于拼接表中的元素,返回值是一个字符串
str =table.concat( tb, ", ")
print(str)--123, 456, 789, 12
--注意点:在concat拼接的表中的元素,可以是数字或者字符串,但是Boolean值在其中,拼接后会报错
13.lua多脚本执行、本地变量、全局变量、_G表
lesson11_require.lua
print("-----------全局变量和本地变量----------")
--全局变量
a=1
b="123"
for i =1,2 do
c="小黑"
end
--在其他语言中,在for循环中定义一个变量,在外界是访问不到的,但是在lua语言中却是可以正常访问
--说明c这个变量是全局变量的概念
print(c)--小黑
--那全是全局变量的话,我们系统内存会爆炸,我们该如何定义一个本地变量呢?--local关键字定义本地变量
for i =1,2 do
local d="小红"
end
--因为d是定义了local本地变量,所以在外界访问的话是访问不到它
print(d)--nil
fun=function ()
local x = 1
print("在函数中定义一个本地变量x:"..x)
end
fun()--本地变量在当前作用域下是完全可以访问到的
print(x)--nil
local y = "嘿嘿"
print("局部变量y:"..y)--局部变量y:嘿嘿
print("-----------多脚本执行------")
--在当前这个脚本我们如何执行另外一个脚本代码呢,也就是如何实现多脚本执行呢--使用require关键字
--require("") 或者 require('')--单双引号都可以
require("Test")--调用另外一个脚本的代码,另外一个脚本里面的代码都会执行一遍
print(n)--18
print(m)--nil 同时访问另一个脚本文件的本地变量是访问不到的,后面要另外一个脚本文件返回才能访问本地变量
print("--------------脚本卸载-------------")
require("Test") --上面执行了一次再次调用你会发现执行不了,加载一次过后就不会再被执行
--返回值是boolean,意思是该脚本是否被执行
print(package.loaded["Test"]) --true
package.loaded["Test"] = nil
print(package.loaded["Test"])--nil
require("Test")--这个一个test测试脚本,分别声明了本地变量和全局变量 我在自身脚本打印一下本地变量m的值5
--require执行一个脚本的时候,可以在脚本最后返回一个外部希望获取的内容(比如脚本的本地变量)
localM = require("Test")
print(localM) --5
print("------------------大G表------------")
--G表示一个总表table,它将未满声明的所有变量都存储在其中
for k,v in pairs(_G) do
print(k,v)
end
--本地变量 加了local的变量不会存到_G表当中
Test.lua
print("这个一个test测试脚本,分别声明了本地变量和全局变量")
local m = 5;
n = 18
print("我在自身脚本打印一下本地变量m的值"..m)
return m
14.特殊用法(多变量赋值、多返回值、模拟三目运算符)
print("------------------多个变量赋值--------------")
a,b,c = 1,2,"123"
print(a)
print(b)
print(c)
--多种变量赋值,如果后面的值不够,会自动补空
a,b,c = 1,2
print(a)
print(b)
print(c)--nil
--多种变量赋值,如果后面的值多了,会自动省略
print("--------------多返回值----------------")
function F1()
return 10,20,30,40
end
--多返回值,你用几个变量接收,就有几个值
--如果少了,就少接收几个,如果多了,就会自动补空
m,n,j,k,l = F1()
print(m)
print(n)
print(j)
print(k)
print(l)--nil
print("-----------and or -----------")
--and or 不仅可以连接bool变量,任何东西都可以连接
--在lua 中,只有nil和FALSE认为是假
--"短路" --对于and来说,有假则假,对于or来说,有真即真
print(1 and 2) --2 第一个1为真,判断2是否为真,为真返回对应的值
print(nil and 2) --nil 第一个nil为假,则返回假
print(false and 3)--false
print(true or 1)--true
print(nil or 2) --2
print("-------模拟三目功能-------")
--lua不支持三目运算符,,但是根据and or特殊用法我们可以实现三目的功能
x = 6
y = 2
local result = (x>y) and x or y
print(result)--6
--(x>y) and x ——> true and x ——> 有假返回假,两个都不假,返回第二个 --》x
--如果x为1,则(x>y) and x-->false; --> false or y -->有真才会返回真 -->y
15.协程
在热更新的时候用协程知识点会少一些,作为了解即可;如果是作为纯lua开发者来说,协程又是非常重要的
print("------------协同程序--------")
print("------协程的创建------------")
--第一种方法:常用方法
--coroutine.create()
fun = function()
print("helloworld")
end
c0 = coroutine.create(fun)
print(c0)--thread: 00B79A38
print(type(c0))--thread
--第二种创建协程的方法 coroutine.wrap
co2=coroutine.wrap(fun)
print(co2)--function: 00B5D378
print(type(co2))--function
--当然了函数的创建也可以放在创建协程方法参数中
co3 = coroutine.create(
function()
print("创建协程")
end)
print(co3)--thread: 00C5E160
print("----------协程的运行--------------")
--返回值是thread类型的协程需要使用coroutine.resume运行协程
coroutine.resume(c0)--helloworld
--返回是function的协程可以使用function直接调用即可
co2() --helloworld
print("-----------------协程的挂起------------")
fun2 = function()
local i = 1
while true do
print(i)
i=i+1
--协程的挂起函数
coroutine.yield(10)
print(coroutine.running)
print(coroutine.status)
end
end
--协程的开启
co4 = coroutine.create(fun2)
--协程的运行
coroutine.resume(co4)--1
coroutine.resume(co4) --2
coroutine.resume(co4) -- 3
--注意点:结果我们发现while循环并没有起到循环的作用,coroutine.yield()起到的是暂停和启动的作用
--协程调用一次,函数循环运行一次(期间会暂停循环),等下次调用的时候把暂停改为启动,在执行一次,由此推下去
co5 = coroutine.wrap(fun2)
co5()--1
co5()--2
print("-------------------")
--同时在coroutine.yield()是可以返回值的,也就是在括号里面传入你需要返回的返回值即可
isOK,temp = coroutine.resume(co4)--1
--返回值类型:默认第一个返回值是协程是否启动成功
--第二个返回值是yield里面的返回值
print(isOK,temp)--true 10
isOK,temp=coroutine.resume(co4) --2
print(isOK,temp)--true 10
isOK,temp=coroutine.resume(co4) -- 3
print(isOK,temp)--true 10
--同理coroutine.wrap()
--这种方式的协程调用也可以有返回值,只是没有默认第一个返回值了
print("返回值:"..co5())--返回值:10
print("返回值:"..co5())--返回值:10
print("返回值:"..co5())--返回值:10
print("----------协程的状态------------")
--coroutine.status(协程对象)
--dead 结束
--suspend 暂停
--running 进行中
--normal 正常
print(coroutine.status(co3))
print(coroutine.status(c0))
--这个函数可以得到当前正在运行的协程的线程号
print(coroutine.running())--没有运行的协程返回nil,在运行的协程必须在函数内部进行打印才会有值
16.元表(终点)__index __newindex __tostring __call 运算符重载
print("------------------元表-----------------")
print("------------------元表概念-----------------")
--任何表变量都可以作为另一个表变量的元素
--任何表变量都可以有自己的元素(父亲)
--当我们子表进行一些特定操作时
--会执行元表中的内容
print("------------设置元表--------------")
meta={}
myTable={}
--设置元表函数
--第一个参数 子表
--第二个参数 元表 (父亲)
setmetatable(myTable,meta)
print("-----------------特定操作-----------------")
print("-----------------特定操作_toString-----------------")
meta2={
--当子表要被当做字符串使用时候,会默认调用这个元表的tostring方法
__tostring = function(t)
return t.name
end
}
myTable2={
name="helloworld"
}
--设置元表函数
--第一个参数 子表
--第二个参数 元表 (父亲)
setmetatable(myTable2,meta2)
--打印的是子表,会对应执行元表的操作内容
print(myTable2)--helloworld
print("-----------------特定操作_call-----------------")
meta3={
--当子表要被当做字符串使用时候,会默认调用这个元表的tostring方法
__tostring = function(t)
return t.name
end,
__call= function(a,b)
print("函数形式调用我就会被执行")--函数形式调用我就会被执行
--第一个参数是函数调用本身(会执行myTable里面的内容)
print(a)-- helloworld
--第二个参数是实际上传进来的参数
print(b)--1
end
}
myTable3={
name="helloworld"
}
--设置元表函数
--第一个参数 子表
--第二个参数 元表 (父亲)
setmetatable(myTable3,meta3)
--把子表当做函数使用,就会调用元表的__call方法
myTable3(1)
print("------------特定操作-运算符重载--------------")
meta4={
--运算符+
__add = function (t1,t2)
return t1.age+t2.age
end,
--运算符-
__sub = function(t1,t2)
return t1.age-t2.age
end,
--运算符*
__mul = function(t1,t2)
return t1.age*t2.age
end,
--运算符/
__div = function(t1,t2)
return t1.age/t2.age
end,
--运算符%
__mod = function(t1,t2)
return 0
end,
--运算符^
__pow = function(t1,t2)
return 0
end,
--运算符==
__eq = function(t1,t2)
return true
end,
--运算符<
__It = function(t1,t2)
return true
end,
--运算符<=
__le = function(t1,t2)
return false
end,
--运算符..
__concat = function(t1,t2)
return "123"--拼接返回的是字符串
end,
}
myTable4={age = 1}
setmetatable(myTable4,meta4)
myTable5={age = 2}
setmetatable(myTable5,meta4)
print(myTable4+myTable5)--2
print(myTable4-myTable5)---1
print(myTable4*myTable5)--2
print(myTable4/myTable5)--0.5
--如果需要使用条件运算符来比较两个对象
--这两个对象的元表一定要一直,才能正确的调用方法
print(myTable4==myTable5)--true
print(myTable4..myTable5)--123
print("-----------特定操作__index和_newIndex---------")
--在开发中,最常用的是__index
meta6Father= {
age = 1
}
meta6Father.__index =meta6Father
meta6 = {
}
--__index的赋值,卸载表外面来初始化
meta6.__index = meta6
myTable6 = {}
setmetatable(meta6,meta6Father)
setmetatable(myTable6,meta6)
--__index当子表中,找不到某一个属性的时候,会到元表中__index指定的表去找属性
--分析执行过程:
--执行myTable6.age的时候,发现在myTable子表中是找不到age属性,所以会到
--元表中__index指定的表(meta6)中去找,发现还是没有找到,会继续找元表meta6Father
--指定的__Index表去找属性,这时候找到了就返回对应的属性咯
--如果一开始,子表就已经有了这个属性的话,就会直接返回了,就不会再次去寻找对应的元表指定到的__index表啦!!!
print(myTable6.age)
--获取当前子表对应的元表的地址
print(getmetatable(myTable6))--table: 00F41790
--rawget 当我们使用这个方法的时候,它会自己身上找有没有这个变量,不会跟其他元素进行寻找,跟本身的任何元表都没有关系了
myTable6.age= 10
print(rawget(myTable6,"age"))--10 给他一个age值,他就会找到啦
print("--------------------------")
--newindex 当赋值的时候,如果赋值一个不存在的索引
--那么会把这个值赋值到newIndex所指的表中,不会修改自己
meta7 ={}
meta7.__newindex = {}
myTable7={}
setmetatable(myTable7,meta7)
myTable7.age = 1
print(myTable7.age)--nil 如果没有指定__newindex的话,这里会打印对应的age值:1
print(meta7.__newindex.age)--1
--rawset方法 会忽略newindex的设置,只会改变自己的变量
rawset(myTable7,"age",100)
print(myTable7.age) --100 修改了自身子表的age属性
17.lua面向对象之封装(重点)
print("------------面向对象--------------")
print("------------封装--------------")
--面向对象 类 其实都是基于table来实现的
--元表相关的知识点
Object={}
Object.id = 1
function Object:Test()
--打印的是Object的id属性
print(self.id)
end
--冒号:是会自动将调用这个函数的对象,作为第一个参数传入的写法
function Object:new()
--self 代表的是我们默认传入的第一个参数
--对象就是变量,返回的是一个新的变量
--返回出去的内容,本质上就是表对象
local obj ={}
--obj.id=3 --如果写上了这个的话,myobj.id=》3,myobj:Test()输出的也是3
--元素知识__index当找自己的变量 找不到时,就会去找元表当中__index所指向的内容
self.__index=self--> self指向的是:Object ==>Object.__index=Object
setmetatable(obj,self)--obj是子表,Object是元表
return obj
end
local myobj = Object:new()--调用new方法,会将Object类型以第一个参数传入形参中
print(myobj) -- table: 00999670
print(myobj.id) -- 1
myobj:Test()--1
--对空表中,声明一个新的属性,叫做id
myobj.id =2--声明了就是在空表obj声明了一个属性,我现在空表obj里面有足够id了,我找到了我就直接返回了,没有找到才会去元表找
myobj:Test() --2
18.lua面向对象之继承(重点)
print("------------面向对象--------------")
print("------------封装--------------")
--面向对象 类 其实都是基于table来实现的
--元表相关的知识点
Object={}
Object.id = 1
function Object:Test()
--打印的是Object的id属性
print(self.id)
end
--冒号:是会自动将调用这个函数的对象,作为第一个参数传入的写法
function Object:new()
--self 代表的是我们默认传入的第一个参数
--对象就是变量,返回的是一个新的变量
--返回出去的内容,本质上就是表对象
local obj ={}
--obj.id=3 --如果写上了这个的话,myobj.id=》3,myobj:Test()输出的也是3
--元素知识__index当找自己的变量 找不到时,就会去找元表当中__index所指向的内容
self.__index=self--> self指向的是:Object ==>Object.__index=Object
setmetatable(obj,self)--obj是子表,Object是元表
return obj
end
--local myobj = Object:new()--调用new方法,会将Object类型以第一个参数传入形参中
--print(myobj) -- table: 00999670
--print(myobj.id) -- 1
--myobj:Test()--1
--对空表中,声明一个新的属性,叫做id
--myobj.id =2--声明了就是在空表obj声明了一个属性,我现在空表obj里面有足够id了,我找到了我就直接返回了,没有找到才会去元表找
--myobj:Test() --2
print("------------------------继承-------------------")
--c#中继承是:class 类名 :继承类
--写一个用于继承的方法
function Object:subClass(ClassName)
--_G知识点是总表,所有声明的全局变量都是以键值对的形式存在其中
--在下面就是声明了传进来的参数类名作为键,空表作为值
_G[ClassName] = {}
--与相关的继承规则
--用到元表的知识点
local obj = _G[ClassName]
self.__index=self --跟封装思想一样,如果你不设置元表的__index,在找元表的时候是找不到元表对应的__index的
setmetatable(obj,self)--元表是Oject:谁调用的谁就是元表
end
Object:subClass("Person")
--print(Person)--table: 00DD9410 打印的是表的地址
--到这里其实Person就是已经继承了Object类了,这时候我们可以在子类中访问父类的方法
--print(Person.id)--1
local p1 = Person:new()
print(p1.id)--1
--[[执行过程:调用new函数的时候,Person()以第一个参数传入new函数中,在里面设置了
Person作为子表也作为元表,即Persn的__index=Person,这时候Person并没有id,这时候会继续往上找
Object:subClass("Person") 这时候会找Object 在OObject类中,Object.__index=Object,在Object找到了id,返回咯!
]]
--给子类单独的设置属性
p1.id=100
print(p1.id)--100 子类的属性和父的属不冲突
p1:Test()--100 打印100的原因是:Test第一个参数是以Person传入,self.id即就是调用Person中的id
--在继承一个类模拟
Object:subClass("Monster")
local m1 =Object:new()
print(m1.id)--1
m1.id=200
print(m1.id)--200
m1:Test()--200
19.lua面向对象之多态(重点)
print("------------面向对象--------------")
print("------------封装--------------")
--面向对象 类 其实都是基于table来实现的
--元表相关的知识点
Object={}
Object.id = 1
function Object:Test()
--打印的是Object的id属性
print(self.id)
end
--冒号:是会自动将调用这个函数的对象,作为第一个参数传入的写法
function Object:new()
--self 代表的是我们默认传入的第一个参数
--对象就是变量,返回的是一个新的变量
--返回出去的内容,本质上就是表对象
local obj ={}
--obj.id=3 --如果写上了这个的话,myobj.id=》3,myobj:Test()输出的也是3
--元素知识__index当找自己的变量 找不到时,就会去找元表当中__index所指向的内容
self.__index=self
setmetatable(obj,self)--obj是子表,Object是元表
return obj
end
--local myobj = Object:new()--调用new方法,会将Object类型以第一个参数传入形参中
--print(myobj) -- table: 00999670
--print(myobj.id) -- 1
--myobj:Test()--1
--对空表中,声明一个新的属性,叫做id
--myobj.id =2--声明了就是在空表obj声明了一个属性,我现在空表obj里面有足够id了,我找到了我就直接返回了,没有找到才会去元表找
--myobj:Test() --2
print("------------------------继承-------------------")
--c#中继承是:class 类名 :继承类
--写一个用于继承的方法
function Object:subClass(ClassName)
--_G知识点是总表,所有声明的全局变量都是以键值对的形式存在其中
--在下面就是声明了传进来的参数类名作为键,空表作为值
_G[ClassName] = {}
--与相关的继承规则
--用到元表的知识点
local obj = _G[ClassName]
self.__index=self --跟封装思想一样,如果你不设置元表的__index,在找元表的时候是找不到元表对应的__index的
--子类 定义一个base属性,base属性代表的就是父类
obj.base=self--这里可以多态的时候可以在子类重写父类方法后也调用父类的方法
setmetatable(obj,self)--元表是Oject:谁调用的谁就是元表
end
Object:subClass("Person")
--print(Person)--table: 00DD9410 打印的是表的地址
--到这里其实Person就是已经继承了Object类了,这时候我们可以在子类中访问父类的方法
--print(Person.id)--1
local p1 = Person:new()
print(p1.id)--1
--[[执行过程:调用new函数的时候,Person()以第一个参数传入new函数中,在里面设置了
Person作为子表也作为元表,即Persn的__index=Person,这时候Person并没有id,这时候会继续往上找
Object:subClass("Person") 这时候会找Object 在OObject类中,Object.__index=Object,在Object找到了id,返回咯!
]]
--给子类单独的设置属性
p1.id=100
print(p1.id)--100 子类的属性和父的属不冲突
p1:Test()--100 打印100的原因是:Test第一个参数是以Person传入,self.id即就是调用Person中的id
--在继承一个类模拟
Object:subClass("Monster")
local m1 =Object:new()
print(m1.id)--1
m1.id=200
print(m1.id)--200
m1:Test()--200
print("-----------------多态--------------")
--多态:子类相对于父类 同一个方法有不同的表现
--相同行为,不同表现就是多态
--相同方法,不同执行逻辑,就是多态
Object:subClass("GameObject")
GameObject.posX = 0;
GameObject.posY = 0;
function GameObject:Move()
--传入的self是子类
self.posX = self.posX+1
self.posY = self.posY+1
print(self.posX)
print(self.posY)
end
GameObject:subClass("Player")
--重写了父类的方法
function Player:Move()
-- 子类调用父类的方法
--这里有一个大坑:
--base指的是GameObject表(类)
--这种方式调用,相对于是把基类表,作为第一个参数传入到方法中
--避免把基类表传入方法中这样相对于就是公用一张表的属性了
--我们如果要执行父类逻辑,我们不要直接使用冒号调用
--要通过.调用,然后自己传入第一个参数
self.base.Move(self) --在subClass方法里面设置了obj.base=self才能子类调用父类方法成功
print("子类独有的MOVE方法")
end
local p1 = Player:new()
p1:Move() --1 1
p1:Move() --2 2
local p3 = Player:new()
p3:Move() -- 1 1
--new出来的对象对应的值是不同的就是有面向对象的思想了,如果每一个new出来的值都公用一个属性值1的话
--那就不是面向对象了
注意点:通过base调用父类方法,一定要记住不要用冒号,通过.的方式,然后将自己传第一个参数进入父类函数内部
20.lua面向对象的汇总
--面向对象实现
--万物之父 所有对象的基类 Object
--封装
Object ={}
--实例化方法
function Object:new ()
local obj={}
--给空对象设置元表,以及__index
self.__index=self
setmetatable(obj,self)
return obj
end
--继承
function Object:subClass(ClassName)
--根据名字生成一张表 就是一个类
_G[ClassName] ={}
local obj = _G[ClassName]
--设置这个的“父类”
obj.base = self
--给子类设置元表,以及__index
self.__index=self
setmetatable(obj,self)
end
--声明一个新的类
Object:subClass("GameObject")
--成员变量
GameObject.posX = 0
GameObject.posY=0
--成员方法
function GameObject:Move()
self.posX=self.posX+1
self.posY=self.posY+1
print("父类的Move方法")
end
--实例化对象使用
local obj= GameObject:new()
print(obj.posX)--0
obj:Move()--父类的Move方法
print(obj.posX)--1
local obj2= GameObject:new()
print(obj2.posX)--0
obj2:Move()--父类的Move方法
print(obj2.posX)--1
--多态
--声明一个新的类Player继承GameObject
GameObject:subClass("Player")
--多态,重写了GameObject的Move方法
function Player:Move()
--调用父类方法,要用.,自己传入第一个参数
self.base.Move(self)
print("子类的Move方法")
end
print("-----多态的测试-----")
--实例化Player对象
local p1 = Player:new()
print(p1.posX)--0
p1:Move()--父类的Move方法 子类的Move方法
print(p1.posX)--1
这一章面向对象特别重要,平常要多加复习!!!
21.自带库
print("---------自带库-------------")
print("---------时间相关的-------------")
--系统时间
print(os.time())
--自己传入参数,得到时间
print(os.time({year=2023,month=4,day=27}))
--利用os.date("*t")方法--获取当前的时间
local nowtime =os.date("*t")
for k,v in pairs(nowtime) do
print(k,v)
end
--获取具体的时间--可以拆分得到
print(nowtime.hour) --16
print(nowtime.day)--27
print(nowtime.year)--2023
print("---------数学运算的-------------")
--绝对值
print(math.abs(-11))
--弧度转角度
print(math.deg(math.pi))--180
--三角函数
print(math.cos(math.pi)) -- -1
--向上向下取整
print(math.floor(2.6)) --2
print(math.ceil(5.2))--6
--最大值最小值
print(math.max(1,2))--2
print(math.min(1,2))--1
--小数分离,分成正数部分和小数部分
print(math.modf(1.2)) -- 1 0.2
--幂运算
print(math.pow(2,5))--32
--随机数--(先设置随机数种子)
math.randomseed(os.time())
print(math.random(100))--这个收到随机数的影响,一直没有改变
print(math.random(100))--这个每次运行都会产生不同的值
--开方
print(math.sqrt(4))--2
print("---------路径相关的-------------")
--package.loaded("脚本名称") 脚本是否加载,脚本卸载
--lua脚本加载路径
print(package.path)
--补充
--package.path = package.path..";C:\\"
22.lua垃圾回收
print("--------垃圾回收--------")
--垃圾回收关键字
--collectgarbage
--代表获取lua占用内存数K字节 用返回值*1024 就可以得到具体的内存占用字节数
print(collectgarbage("count"))--19.7548828125
--通过这个我们在开发中可以查看当前内存占用的情况
--进行垃圾回收
collectgarbage("collect")
test={id=1,name="123123"}
test2={id=1,name="123123"}
--变为垃圾对象置空即可
test = nil
test2=nil
--lua中的机制和c#的垃圾回收机制很类似,解除羁绊,就是变为垃圾了
print(collectgarbage("count"))--这个值垃圾回收了内容占比会变小
--lua中 有自动定时的进行GC的方法
--Unity中热更新开发,进来不要去自动去垃圾回收 但是好性能-不建议使用