因为实习的项目用到了Lua,所以再来深入学习一下
函数
函数的的多返回值
Lua中的函数可以实现多返回值,实现方法是再return后列出要返回的值的列表,返回值也可以通过变量接收到,变量不够也不会影响接收对应位置的返回值
Lua中传入参数和参数个数不匹配也不会报错,只会补空或丢弃
函数的重载
在Lua中他是不支持函数的重载的,默认调用最后声明的这个函数,但是使用重载的方法也不会报错
变长参数
变长参数在函数内使用时需要先用一个表存起来,然后再去操作
function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组
s = s + v
end
return s
end
print(add(3,4,5,6,7)) --->25
函数嵌套
function F8(x)
return function(y)//不需要命名
return x+y
end
end
f9=F8(10)//相当于返回一个函数类型的变量
print(f9(5))//15
这也是Lua中闭包的体现
改变传入参数的生命周期
传入参数x本应在执行完之后被销毁,但因为闭包改变了x的生命周期让其可以持续存在
table
lua中所有的复杂类型都是table,数组二维数组字典类都是用table实现的
数组
lua中索引从1开始
a={1,2,4,“123”}
#是通用的获取长度的关键字
如果表中某一位为nil会影响#获取长度(5.1已改),长度从nil开始截断
二维数组
a={{1,2,3},{4,5,6}}
for i=1,#a do
b=a[i]
for j=1,#b do
print(b[j])
end
end
table实现类
lua中复杂一点的数据结构都是通过类来实现的
lua中没有面向对象的概念,需要我们自己来实现
Student={
age=1,
sex=true,
Up=function()
//函数体
//在表内部的函数调用表内部的变量必须要指定是谁的
print(Student.age)
end,
Learn=function(t)
//把自己作为参数传进来
print(t.age)
//函数体
end
}
//不像C#一样需要new一个对象来实现调用
Student.Up()
//可以在表外申明表的变量和方法
Student.name="111"
Student.Speak=function()
end
function Student.Speak2()
end
//冒号会把调用者作为第一个参数传入
//如果使用冒号申明函数,相当于有一个默认参数
Student:Learn()
Student.Learn(Student)
协程
协程的创建
协程的创建一般依附于一个函数
协程的本质是一个线程对象
--常用方式
--返回的是一个线程
fun=function()
end
co=coroutine.create(fun)
--返回的是一个函数
co2=coroutine.wrap(fun)
协程的运行
两种创建方式对应的运行方式也不同
coroutine.resume(co)
--因为协程返回的是一个函数,所以可以直接运行
co2()
协程的挂起
fun2=function()
while ture do
print("123")
--挂起函数
coroutine.yield()
//协程也可以返回值
--coroutinre.yield(i)
end
end
co3=coroutine.create(fun2)
--区别于C#的协程,lua中的协程每调用一次执行一次对应的函数
coroutine.resume(co3)
-- 默认第一个返回值是协程是否启动成功
--这种方式的协程调用也可以有返回值,只是没有默认第一个返回值了
co4=coroutine.wrap(fun2)
co4()
co4()
协程的状态
dead结束
running运行
suspended暂停
coroutine.status(协程对象)
--获取正在运行的协程的协程号
coroutine.running()
元表
元表概念
任何表变量都可以作为另一个表变量的元表
任何表变量都可以有自己的元表
当我们子表(有元表的表)进行一定操作时会执行元表中的内容
设置元表
mete={}
mytable={}
--设置元表函数,第一个参数子表,第二个参数元表
setmetatable(mete,mytable)
getmetatable(mete)--获得子表对应的元表
元表的特定操作
__tostring(用的多)
mete={
--当子表要作为字符串使用时,会默认调用元表中的__tostring 方法
__tostring=function(t)
return t.name
end
}
mytable={
name="123"
}
setmetatable(mete,mytable)
print(mytable)--输出123
__call(用的多)
mete={
--当子表被当作一个函数来使用时,会默认调用这个__call中的内容
--当希望传参数时第一个参数一定是调用者自己
__call=function(a,b)
print(a)
print(b)
end
}
mytable={
name="123"
}
setmetatable(mete,mytable)
print(mytable)
--输出123
1
运算符重载(用的可能不多)
mete={
--相当于运算符重载 当子表使用对应运算符时会调用该方法
-- +
__add=function(a,b)
return a.name+b.name
end,
-- ==调用的两个表的元表必须一致,才能准确调用此方法
__eq=function(a,b)
return true
end
}
mytable={
name=1
}
mytable2={
name=2
}
setmetatable(mete,mytable)
print(mytable+mytable2)
--输出123
1
__index
mete={
}
meta.__index={age=2}
-- __index的赋值写在表外面来初始化,和C++的构造函数不可以是虚函数一个道理,自己都没有初始化好要怎么指向自己内部的东西呢
mytable={
}
setmetatable(mete,mytable)
__index:当子表中找不到某一个属性时,会到元表中__index指定的表去找属性
print(mytable.age)--输出2
__index还可以实现“套娃”,当子表中找不到某一个属性时,会到元表中找这个属性,如果元表中也找不到该属性则会到元表的元表中去寻找
__newIndex
__newIndex当赋值时,如果赋值一个不存在的索引,那么会把这个值赋值到__newIndex所指的表中,不会修改自己
mete={
}
meta.__newIndex={}
mytable={
}
setmetatable(mete,mytable)
mytable.age=1
print(mytable.age)--输出2
使用rawget和rawset可以直接设置对应的表,而非索引指向的元表
实现面向对象
表就是表现类的一种形式
在Lua中,冒号语法糖用于简化方法的定义和调用,自动传递self参数。当用冒号定义方法时,实际上隐式地添加了self作为第一个参数。例如,obj:method() 转换成 obj.method(obj)
如果用点号调用时冒号声明的方法,需要显式传递self。
封装
声明对象实际上是声明了一张空表,获取属性是通过设置元表后获取元表的属性实现的
--self代表的是我们默认传入的第一个参数
--对象就是变量,返回一个新的变量
--返回出去的内容本质是一个表对象
Object={}
Object.id=1
function Object:Test()
print(self.id)
end
function Object:new()
local obj={}
--当子表中找不到某一个属性时,会到元表中__index指定的表去找属性
self.__index=self
setmetatable(obj,self)
return obj
end
local myobj=Object:new()
myobj:Test()--输出1
对空表中申明一个新的属性叫做id
myobj.id=2
myobj:Test()--输出2
继承
接上文
_G来根据字符串创建一个新的表(类)
function Object:subClass(classNmae)
-- _G是总表 所有声明的全局标量 都以键值对的形式存在在其中
_G[className]={}
local obj=_G[className]
self.__index=self
setmetatable(obj,self)
end
Object:subClass("Person")
local p1=Person:new()
print(p1.id)--输出1
多态
多态的本质是相同的方法执行不同的逻辑
代码接上文
方法1:子类直接重写这个方法
方法2:通过给子类添加base属性保留父类逻辑执行
function Object:subClass(classNmae)
-- _G是总表 所有声明的全局标量 都以键值对的形式存在在其中
_G[className]={}
local obj=_G[className]
self.__index=self
-- 为子类定义base属性 base属性代表父类
obj.base=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
end
Object:subClass("Player")
function Player:Move()
-- base指的是GameObject表(类)
-- 这种调用方式相当于是把基类表作为第一个参数传入了方法中
self.base:Move()
end
local p1=Player:new()
p1:Move()
local p2=Player:new()
p2:Move()
目前这种写法有坑,不同对象使用的成员变量是相同的成员变量,不是自己的
更正版
function Player:Move()
-- 如果我们要执行父类逻辑,则需要避免将父类传入到方法中
-- 所以要使用.去调用,自己传入第一个参数
self.base.Move(self)
end
总结
Object={}
Object.id=1
function Object:new()
local obj={}
--给空对象设置元表和__index
self.__index=self
setmetatable(obj,self)
return obj
end
function Object:subClass(classNmae)
-- 根据名字生成一张表,即一个类
_G[className]={}
local obj=_G[className]
--给子类设置元表以及__index
self.__index=self
--设置自己的父类
obj.base=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
end
-- 实例化对象使用
local obj=GameObject:new()
obj:Move()
--申明一个新的类,Player继承GameObject
Object:subClass("Player")
--重写了Move方法
function Player:Move()
--base调用父类方法,自己传第一个参数
self.base.Move(self)
end
local p1=Player:new()
p1:Move()
垃圾回收
collectgarbage
test={id=1}
-- lua中的机制和垃圾回收很类似,置空之后就认定为垃圾
test=nil
--进行垃圾回收,理解有点像C#的GC
collectgarbage("collect")
--获取当前lua占用内存数
collectgarbage("count")
lua中有自动定时进行GC的方法
但是在热更新开发中通常不会使用自动GC,而是选择在内存占用量达到一定程度时手动GC,减少性能损耗