Lua再学习

发布于:2025-05-12 ⋅ 阅读:(21) ⋅ 点赞:(0)

因为实习的项目用到了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,减少性能损耗


网站公告

今日签到

点亮在社区的每一天
去签到