一、Lua是什么?
1.1 Lua的历史
Lua是由Roberto Ierusalimschy、Luiz Henrique de Figueiredo和Waldemar Celes于1993年创建的,当时他们是巴西里约热内卢天主教大学计算机图形技术组(Tecgraf)的成员。在开发Lua之前,他们曾参与过数据描述/配置语言"SOL"(简单对象语言)和"DEL"(数据录入语言)的工作。这两种语言是他们独立开发的两个不同项目的一部分,旨在增强工程应用的交互式图形程序。然而,SOL和DEL缺乏许多控制流结构,因此需要增加完整的编程能力。因此,为了满足这个需求,他们创建了Lua。
在《The Evolution of Lua》中,Lua的作者写道,在1993年,Tcl是Lua的唯一真正竞争者,但Tcl的语法相对较为陌生,且只能在Unix平台上运行,不太适合数据描述。此外,LISP和Scheme的语法也不够友好,Python还处于起步阶段。在Tecgraf的自由、自主的氛围下,他们自然而然地尝试开发自己的脚本语言。考虑到这门语言的潜在用户不一定是专业程序员,因此他们希望语言避免复杂的语法和语义。另外,由于Tecgraf的客户使用各种不同的计算机平台,他们希望新语言的实现能够高度可移植。最后,考虑到Tecgraf的其他产品也需要嵌入脚本语言,他们决定让新语言像SOL一样提供带有C API的库。
1.2 Lua的特点
Lua 作为一种脚本语言具有多种特点,以下是其中一些主要的特点:
- 轻量级和可嵌入性:Lua 的核心非常小巧,可以轻松地嵌入到其他应用程序中,作为脚本语言使用。这使得 Lua
在游戏开发、嵌入式系统和其他领域中得到广泛应用。 - 简洁清晰的语法:Lua 的语法简洁明了,易于学习和使用。它没有过多的冗余符号,采用基于表达式的语法,没有大括号,使用 end
关键字来结束块,使得代码更加清晰易读。 - 动态类型和自动内存管理:Lua 是一种动态类型语言(运行时进行变量类型检查和解析),变量不需要显式声明类型。它还具有自动内存管理,通过垃圾回收机制来管理内存,简化了开发者的工作。
- 强大的数据描述能力:Lua 支持多种基本数据类型,包括数字、字符串、布尔值、表(类似于字典或关联数组)、函数等。表是 Lua
中最重要的数据结构之一,可以灵活地表示复杂的数据集合。 - 灵活的扩展能力:Lua 提供了丰富的扩展接口,可以通过 C/C++ 编写扩展模块,并与 Lua 脚本进行交互。这种可扩展性使得 Lua
可以与其他语言轻松集成,满足不同应用场景的需求。 - 高性能:Lua 的解释器经过优化,执行速度较快,适合用于处理大规模的数据和复杂的逻辑。
- 动态代码执行:Lua 支持动态代码执行,可以通过 load 和 loadfile 函数动态加载并执行 Lua 脚本,灵活性很高。
- 适用于嵌入式系统:由于 Lua 的轻量级和可嵌入性,它在嵌入式系统中得到广泛应用,可以用于控制设备、配置参数等任务。
二、Lua语法
2.1 输出print、单行注释、多行注释
lua
print("hello world")
--单行注释
--[[
多行注释
多行注释
]]
2.2 变量
- lua中四种简单的变量类型:nil、string、boolean、number。除此之外还有:function、table、userdata、thread。
- 不需要声明变量类型;可通过type函数获取变量类型,type函数返回值是string。
- 变量可随便赋值。Lua 中的变量是动态类型的,这意味着变量没有预定义的类型,类型会在运行时根据赋给变量的值来决定。
- lua中没有赋值过的变量,默认类型是nil。
2.3 字符串
- 在 Lua 中,字符串可以用双引号"、单引号'或者长括号[[ ]]来表示。
lua
a="hello"
print(a)
b=[[hello]]
print(b)
- 使用#运算符可以获取字符串长度。
lua
a="hello world"
print(#a)
3.可以使用...运算符来拼接字符串。也可以使用string.format来进行拼接。
lua
a="一"
print("一个汉字长度:"..#a) --一个汉字长度是3
print(string.format("今年 %s 岁",'18'))
4.多行打印
在Lua中,可以使用多行字符串来打印多行文本。多行字符串可以通过在字符串的开始和结束使用双方括号[[和]]来创建。也可以通过行末尾添加"\n"来进行多行打印。
lua
a=[[1.hello
2.world]]
print(a)
5.类型转换
在Lua中,可以使用 tostring 函数将一个值转换为字符串。这个函数可以接受任何基本类型的值作为参数,并返回该值的字符串表示形式。
lua
a=false
print(tostring(a))
6.字符串操作(大小写、反转、查找、截取、重复)
字符串相关操作,不会改变原字符串。Lua中索引从1开始。
lua
str="YsL"
str1=string.upper(str)
print("原字符串:"..str..",全部大写为:"..str1)
str1=string.lower(str)
print("原字符串:"..str..",全部小写为:"..str1)
str1=string.reverse(str)
print("原字符串:"..str..",反转字符串为:"..str1)
--原字符串:YsL,全部大写为:YSL
--原字符串:YsL,全部小写为:ysl
--原字符串:YsL,反转字符串为:LsY
str="YsL"
print("---------字符串查找")
print(string.find(str,"sL")) --2 3
print("---------字符串截取")
print(string.sub(str,2)) --sL
print(string.sub(str,2,2)) --s
print("---------字符串重复:"..string.rep(str,3)) --字符串重复:YsLYsLYsL
print("---------字符串修改")
print(string.gsub("Session",'s','*'))
--Se**ion 2
--string.gsub()返回两个结果,第二个值表示修改了几个字符。
7.字符串与ASCII互转
lua
a=string.byte("Lua",1)
print(a) --76
a=string.char(a)
print(a) --L
2.4 function
在Lua中,函数是一种可以封装代码以便重复使用的基本结构。函数可以接受参数,并且可以返回一个或多个值。Lua不支持重载,但重载后并不报错,默认调用最后声明的函数。
1.无参无返回值
lua
--先声明
function fun1()
print("my fun1")
end
--再调用
fun1()
2.有参无返回值
lua
function fun3(a)
print(a)
end
fun3(1)
fun3("hello world")
fun3(true)
fun3(nil) --nil
fun3() --nil
fun3(1,2) --1 实参和形参个数不匹配,会补空或丢弃
3.有一个返回值
lua
function fun4(a) --直接return即可
return a
end
temp=fun4(1)
print(temp)
4.有多个返回值
lua
function fun5(a) --支持返回多个值
return a,-1
end
temp1,temp2,temp3=fun5(10) --支持多个变量来接取返回值
print(temp1)
print(temp2)
print(temp3) --nil
5.匿名函数
lua
fun2=function()
print("my fun2")
end
fun2()
6.可变形参函数
lua
function fun7(...)
arg={...} --arg表存储参数
for i=1,#arg do
print(arg[i])
end
end
fun7(1,2,3,4,"hello",false)
7.获取函数类型
lua
fun6=function()
print("my fun6")
end
print(type(fun6)) --function
8.函数嵌套
lua
function fun8()
return function()
print("函数嵌套")
end
end
--调用方式1
fun9=fun8()
fun9()
--调用方式2
fun8()()
9.函数嵌套-闭包
在Lua中,函数可以嵌套在其他函数内部,这时候内部的函数可以访问外部函数的局部变量,这种结构称为闭包(closure)。闭包的强大之处在于,即使外部函数的执行已经结束,闭包仍然可以记住外部函数中的变量。
lua
function fun10(x)
return function(y)
print(x+y) --临时变量x的生命周期变长
end
end
close=fun10(1)
close(2)
2.4 table
表是 Lua 中唯一的数据结构,可以用来表示数组、字典等复杂数据结构。表在 Lua 中是非常强大的,因为它们可以包含所有类型的值。
1.一维数组
lua
a={1,2,3,4,nil,true,"hello",nil} --8个元素 如果数组的最后一个元素是 nil,Lua 通常会将其视为数组的结束,导致长度计算时忽略该 nil 元素。
print("数组a的长度:"..#a) --7 lua5.1.5版本中,nil如果是数组最后一个元素,计算数组长度时则会忽略该元素。用#获计算长度不准确
print(a[0]) --nil lua索引从1开始
print("第一个元素:"..a[1]) --1
print("---------------一维数组遍历")
for i=1,#a do
print(a[i])
end
2.unpack函数
在Lua中,unpack函数是一个非常有用的内置函数,它可以从表中取出一个连续的序列,并返回这些值。这个函数在Lua 5.1中是全局的,但从Lua 5.2开始,它被移到了table库中,即table.unpack。
lua
a = {1,2,3,4,nil,true,"hello",nil}
print(unpack(a)) --1 2 3 4 nil true hello
使用unpack可以很方便地将表中的值作为多个参数传递给函数。
unpack或table.unpack接受三个参数:表、起始索引和结束索引。如果不指定起始索引和结束索引,默认情况下它会从第一个元素开始,到表的长度结束。
3.二维数组
lua
a={{1,2,3},{4,nil,"hello"}}
print(a[1][3]) --3
print(a[2][3]) --hello
print("---------------二维数组遍历")
for i=1,#a do
b=a[i]
for j=1,#b do
print(b[j])
end
end
4.自定义索引
Lua的表不仅可以使用整数索引(不建议自定义整数索引),还可以使用字符串或其他Lua值(除了nil)作为键。
lua
a={[0]=1,2,3,[-1]=4,[4]=5}
print(#a) --2 会去除索引不正常的元素
print(a[0]) --1
for i=1,#a do
print(a[i]) --2 3
end
a={[1]=1,[2]=1,[4]=1,[6]=1}
print(#a) --6
print(unpack(a)) --1 1 nil 1 nil 1
5.迭代器遍历
使用#计算长度并不准确,建议使用迭代器遍历:
- 如果想要处理的是严格意义上的数组(索引连续的列表),使用ipairs更合适,因为它的性能可能会更好一些。
- 如果需要处理的是一个混合了数组和字典风格的表,或者你不确定表的索引是否连续,那么使用pairs会更安全,它能确保遍历到表中的所有元素(nil除外)
lua
a={[0]=0,1,2,3,nil,4,5,[10]=6,c=7,nil}
for i,k in ipairs(a) do
print("i:"..i.."-k:"..k)
end
-- i:1-k:1
-- i:2-k:2
-- i:3-k:3
lua
a={[0]=0,1,2,3,nil,4,5,[10]=6,c=7,nil}
for i,k in pairs(a) do
print("i:"..i.."-k:"..k)
end
--i:1-k:1
--i:2-k:2
--i:3-k:3
--i:5-k:4
--i:6-k:5
--i:0-k:0
--i:10-k:6
--i:c-k:7
6.字典
在Lua中,并没有专门的"字典"类型,但是表(table)结构在Lua中是非常灵活的,通常用来实现类似字典的功能。表可以存储键值对,其中键和值可以是任意类型的数据(除了nil)。当使用字符串作为键时,表就类似于其他语言中的字典或哈希表。
lua
a={["name"]="Lisa",["age"]=22,["sex"]="girl",["1"]=true,c=56}
print("-------------访问")
print(a["name"])
print(a.c)
print(a.name)--虽然可以通过这种方法得到值,但键不能是数字,如a.1不对
print("-------------修改")
a["age"]=20
print(a["age"])
print("-------------新增")
a["Country"]="TaiLand"
print(a.Country)
print("-------------删除")
a["Country"]=nil
print(a.Country)
print("-------------字典的遍历,使用pairs")
for i,k in pairs(a) do
print(i,k)
end
7.类
Lua中默认没有面向对象,所以不能new。
lua
Student={
age=12,
name="Lisa",
fun0=function(t) --在函数中需要调用表本身的属性和方法时,可以把自己当参数传进来
print("fun0:"..t.name..":"..t.age)
end,
fun1=function()
--print("fun1:"..name..":"..age) --错误
print("fun1:"..Student.name..":"..Student.age) --在函数中调用表本身的属性和方法,需要指明表名称
end
}
--新增变量
Student.sex="girl"
print(Student.age)
print(Student.sex)
Student.fun0(Student)
Student:fun0() --语法糖:冒号调用方法时,会默认把调用者作为第一个参数传入方法中
Student.fun1()
类函数声明 :和普通函数申明的两种方式(普通函数、匿名函数)相同,除此之外,也可以使用:来申明。
- 当声明一个方法时,使用 : 语法允许定义一个需要隐式 self 参数的函数。self 实际上指的是调用方法的对象实例,而不是类名。
- 当调用一个方法时,如果使用 : 语法,Lua会自动将调用该方法的表(对象)作为第一个参数传递给该方法。
lua
Student.fun2=function()
print("fun2")
end
function Student.fun3()
print("fun3")
end
function Student:fun4() --语法糖:冒号声明方法时,会默认把调用者作为第一个参数传入方法中。self表示默认传入的第一个参数,self不是this。(self 实际上指的是调用方法的对象实例,而不是类名)
print("fun4:"..self.name)
end
Student.fun2()
Student.fun3()
Student:fun4()
Student.fun4(Student)
8.表操作
| 操作 | 代码 | 备注 |
|---|---|---|
| 插入 | insert | |
| 删除 | remove | 默认删除最后一个元素 |
| 排序 | sort | 排序操作会直接改变原始表,而不是创建一个已排序的新表。 |
| 拼接 | concat | table.concat函数用于将表中的元素连接成一个字符串。你可以指定一个连接符(也称为分隔符),用来在合并时插入元素之间。如果不指定连接符,默认使用空字符串(也就是没有分隔符)。 table.concat还可以接受第二个和第三个可选参数,分别代表起始索引和结束索引,用于指定连接表中的哪一部分。 |
lua
t1={{name=1,age=10},{name=2,age=11}}
t2={name=3,age=12}
print(#t1) --2
table.insert(t1,t2)
print(#t1) --3
print(t1[3]["age"]) --12
table.remove(t1) --默认删除最后一个元素
table.remove(t1,1) --第二个参数,指定移除的元素索引
t3={12,53,6,2,4}
table.sort(t3)
print(unpack(t3)) --2 4 6 12 53
t4={1,"hello","world"}
str=table.concat( t4, "-", 1, 3 )
print(str) --1-hello-world
2.5 协程
Lua中的thread类型是指协同程序(coroutines),它们是由coroutine库提供的。在Lua中,术语"thread"通常指的是这些协同程序,而不是操作系统级别的线程。Lua的协同程序是协作式的,而非抢占式的,这意味着它们不会自行中断以让其他线程运行,而是显式地通过yield和resume来交换控制权。(Lua的线程可以让你编写出看起来像是同时执行的代码,但实际上它们是分时执行的,一个线程让出控制权后,另一个线程才会开始执行。)
Unity 协程是与引擎的事件循环集成在一起的,允许在等待时继续处理其他事件和渲染更新。这与 Lua 中需要显式地调用 coroutine.resume 来恢复协程的执行有很大的不同。在 Unity 中,你只需要启动协程,引擎会在合适的时间点自动恢复它的执行。 Lua中使用coroutine.create创建协程,每执行一次coroutine.resume,协程函数会前进至下一个coroutine.yield。
1.协程创建
- coroutine.create():常用方式,返回线程类,协程的本质是线程对象
lua
fun=function()
print("hello 0223")
end
co=coroutine.create(fun)
print(type(co)) --thread
- coroutine.wrap():返回函数类型
lua
fun=function()
print("hello 0223")
end
co1=coroutine.wrap(fun)
print(type(co1))--function
2.协程运行
对应两种创建方式,协程运行也不同:
- coroutine.resume:对应create创建方式
- 按函数方式运行
lua
coroutine.resume(co) --对应create创建方式
co1() --按函数方式运行
3.协程挂起
lua
fun2=function ()
local i=1
while true do
print(i)
i=i+1
--挂起
coroutine.yield(i) --返回i
end
end
co2=coroutine.create(fun2)
temp1,temp2=coroutine.resume(co2)
print(temp1,temp2) --true 2 --true表示协程运行成功,temp2才是返回值i
temp1,temp2=coroutine.resume(co2) --运行一次执行一次
print(temp1,temp2) --true 3
4.协程状态
-
coroutine.status(co)返回协同程序co的状态,可能的值有running、suspended、normal和dead。
-
coroutine.running() 得到正在运行的协程编号,要在函数内部打印。
coroutine.wrap 返回的是一个函数,而不是协程对象,所以不能对它使用 coroutine.status。因为 coroutine.status 期望它的参数是一个协程,而不是一个函数。
lua
fun=function()
print("hello 0223")
print(coroutine.running())
end
co=coroutine.create(fun)
coroutine.resume(co)
print(coroutine.status(co))
-- hello 0223
-- thread: 00D5E500
-- dead
lua
fun=function()
print("hello 0223")
print(coroutine.running())
coroutine.yield("hello")
end
co=coroutine.create(fun)
coroutine.resume(co)
print(coroutine.status(co))
-- hello 0223
-- thread: 00C9DD90
-- suspended