Lua基础

Lua 教程 | 菜鸟教程(https://www.runoob.com/lua/lua-tutorial.html) 嫌啰嗦可以看这个

一、简单变量

分为:nil,number,string,boolean。

1.变量申明

lua中所有的变量申明,都不需要申明变量类型,它会自动判断类型。而且可以随便赋值

1
a=1

2.nil

nil 类似于C#中的null,空的概念,也等同于false

1
2
3
a=nil
print(a)
--打印结果为nil

3.number

所有的数值都是number。默认只有一种double类型

1
2
3
4
5
a=1
print(a)
a=1.2
print(a)
--打印结果为:1 1.2

4.string

字符串的声明 使用单引号或者双引号,都能声明。

lua中没有char

1
2
a='123'
print(a)

5. boolean

包含两个值false和true。

1
2
3
a=true
print(a)
--打印结果为:true

6.如何得到变量类型

通过type函数 我们可以得到变量的类型。

返回值是string类型

注:lua 中没有使用声明过的变量,不会报错默认值是 nil。

二、字符串操作

语法:#字符串变量

1
2
3
4
s = "123123"
print(#s)
--6
--输出字符串长度

注:英文字符占1个长度,汉字占3个长度

注:lua中也是支持转义字符的

1. 字符串多行打印

转义字符

或[[]]

1
2
3
4
5
6
print("111\n123")
s=[[
123
123
123
]]

2. 字符串拼接

通过..拼接 或string.format()函数

1
2
3
4
5
6
7
8
9
10
print("123" .. "456")
--打印:123456
s1="123"
s2="456"
print(s1 .. s2)

print(string.format("我今年%d岁了"18))
--%d:与数字连接
--%a:与任何字符拼接
--%s:与字符配对

3. 别的类型转字符串

默认打印自动转string,也可以用函数tostring()显示转换

1
2
a = true
print(tostring(a))

4.string公共方法

注:这些方法都不会改变原字符串,只是返回一个新字符串

(1) 小写转大写

string.upper

1
2
str=“abcdefg”
print(string.upper(str))

(2) 大写转小写

string.lower

1
2
str=“ABCD”
print(string.lower(str))

(3) 翻转字符串

string.reverse()

1
2
3
str=“ABCD”
string.reverse(str)
--结果DCBA

(4)字符串索引查找

string.find(str,”cde”)

1
2
3
str=“ABCD”
string.find(str,BCD)
--结果为2 4

(5) 截取字符串

string.sub,有两个重载

1
2
3
str=“ABCD”
string.sub(str,3)--C
string.sub(str,2,4)--BCD

(6) 字符串修改

string.gsub(str,要修改的子串,要替换的字符串)

1
2
str=“ABCD”
print(string.gsub(str,"AB","=="))--==CD

(7) 字符转 ASCLL码

string.byte

1
2
3
4
str=“ABCD”
print(string.byte("Lua",1)))--把第一个字符转为ASCLL码

print(string.char(123)) --ASCLL转字符

三、运算符

1. 算数运算符

与C#大体都一致

注:Lua中没有自增自减,不支持++或--

注:没有复合运算符 += -= /= *=

注:Lua中会将字符串自动转为数值进行计算

因为number类型为double所以1/2为5,而不是0

1
print("123"+1)--124

特殊:lua中有幂运算

^

1
print(2^2)--4

2. 条件运算符

大体与c#一致,不同为不等于,~=为不等于

3. 逻辑运算

与是and 或是or 非为not 支持短路

C#中 && || !

4.位运算符

不支持,需要自己实现

5.三目运算符

? : 不支持

四、条件分支语句

if 条件 then ……end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a=1
--单分支
if a > 5 then
print("123")
end

--双分支
if a > 5 then
print("123")
else
print("456")
end

--多分支
if a < 5 then
print(123)
--lua中 elseif 一定是连着写的 否则报错
elseif a == 6 then
print(6)
elseif a == 7 then
print(7)
else
print("456")
end

注:条件分支就像填空一样,先把then end写完之后往里填。最开始的then end就像大括号一样

注:lua中没有switch语法 需要自己实现

五、循环语句

1.while语句

while 条件 do…..end

1
2
3
4
while num <5 do
print(num)
num = num+1
end

2. do…while 语句

repeat ….until 条件 (z注意:条件是结束条件)

1
2
3
4
5
num = 0
repeat
print(num)
num= num+1
until num>5 --满足条件跳出 结束条件

3. for循环

for 变量= 起始值,终止值,每次递增的数值(不写默认为1) do ….. end

注:lua中 for循环默认递增 i会默认+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i=1,5 do 
print(i)
end
--12345

--自定义增量 直接在逗号后面写
for i=1,5,2 do
print(i)
end
--135

--自减
for i=5,1,-1 do
print(i)
end

六、函数变长嵌套闭包多返回值

1.函数

function 函数名()

end

a = function()–有点类似 C#中的事件和委托

end

2. 无参数无返回值

1
2
3
4
5
6
--函数申明
function F1()
print("F1函数")
end
--调用函数
F1()
1
2
3
F2 =function()
print("F2函数")
F2()

3.有参数

1
2
3
4
5
6
7
8
9
function F3(a)
print(a)
end
F3(1)
F3("223")
F3(true)
--1
--223
--true

如果你传入的参数 和函数参数个数不匹配

不会报错只会补空nil 或者丢弃

4.有返回值

函数声明是不需要规定返回值类型的,你直接返回,外面直接 接取就行。

1
2
3
4
5
function F4(a)
return a
end
temp = F4("123")
prent(temp)

多返回值时 在前面申明多个变量接取即可

如果变量不够 不影响 值接取对应位置的返回值

如果变量多了也不影响,直接赋nil

1
2
3
4
5
function F4(a)
return a,"123",true
end
temp1,temp2,temp3 = F4("123")
prent(temp)

5.函数的类型

就是function

1
2
3
4
5
F5 = function()
print("123")
end
--匿名函数
print(type(F5))

6.函数的重载

函数名相同 参数类型不同 或者参数个数不同

注:Lua中函数不支持重载,默认调用最后一个声明的函数

7.变长参数

function 函数名 (…) 输入类型随便

1
2
3
4
5
6
7
8
9
function F7(...)
--变长参数使用 用一个表存起来 再用
arg={...}
for i=1,#arg do
print(arg[i])
end
end
F7(1,2,3,4,5,6)--123456
F7(1,"123",true)--1 123 true

8.函数嵌套

函数内部定义函数

1
2
3
4
5
6
7
function F8()
F9= function ()
print(123); --在函数内部声明了一个匿名函数
end
end
f9=F8()
f9()

9.闭包(面试考点)后面详细补充

在函数里面返回一个函数,改变大函数变量的生命周期。

子函数可以使用父函数中的局部变量,这种行为叫做闭包。

1
2
3
4
5
6
function F9(x)
--改变x传入参数的生命周期
return function(y)
return x+y
end
end

七、复杂数据类型——table表

所有的复杂类型都是table(表):数组、字典、类等等。

1.数组

声明:a = {1,2,3,4,”123”,true,nil}

注:lua中索引从1开始

注:#是通用的获取长度关键字,并且在打印长度时nil被忽略只要遍历到nil,就认为数组断了。所以用#获取长度并不准确。

2.数组遍历

1
2
3
for i=1,#a do
print(a[i])
end

3.二维数组

表中表,一个大括号包含n个小括号

1
2
3
4
a = {{1,2,3},
{2,3,4},
{3,5,6}
}

4.二维数组遍历

1
2
3
4
5
6
for i=1,#a do
b=a[i] --先遍历每个子数组,取出来方便#计算
for j=1,#b do -- 再遍历子数组访问数据
print(b)
end
end

5.自定义索引

1
aa ={[0]=1,2,3,[-1]=4,5}

注:如果用#aa计算 数组长度只能得到3,因为这里面0和-1编辑器不认识。#默认从1开始计算,没有自定义的会从1开始一个一个排

1
2
3
4
5
aa ={[0]=1,2,3,[-1]=4,5}
print(aa[1])
print(aa[2])
print(aa[3])
--输出:2,3,5
  • 深坑:如果自定义隔一个数定义,lua默认空的是nil

    并且会计算入数组长度。如果隔一个以上则直接断开,了解即可

6. 迭代器遍历

因为用#获取表长不准确,所以迭代器遍历 主要是用来遍历表的。

(1) ipairs

ipairs遍历 还是 从1 开始往后遍历的 小于等于0的值得不到。并且只能找到连续的索引。如果中间断了 ,他无法遍历出后面的内容

1
2
3
4
5
6
7
a = {[0]=1,2,[-1]=3}
for i,k in ipairs(a) do
print('ipairs遍历键值'..i.."-"..k)
--i相当于键,k相当于值。
end

--ipairs遍历键值1-2

(2) pairs

pairs能把所有的键都找到,建议使用它遍历各种不规则表。

1
2
3
4
5
6
7
8
a = {[0]=1,2,[-1]=3}
for i,k in pairs(a) do
print('pairs遍历键值'..i.."-"..k)
--i相当于键,k相当于值。
end
--pairs遍历键值1-2
--pairs遍历键值0-1
--pairs遍历键值-1-3

(3)ipairs和pairs区别(面试考点)

后续补充

八、用table表示字典

1.字典的声明

字典由键值对构成

访问单个变量 用中括号填键来访问

还可以类似 . 成员变量的形式得到值(不能是数字)

1
2
3
4
5
6
7
8
9
10
a = {["name"]="wqx",["age"]=14,["1"]=5}
print(a["name"])
print(a["age"])
print(a.name)
--修改
a["name"]="qwe"
--新增 直接加key 赋值就行
a["sex"]=false
--删除
a["sex"] = nil

2.字典遍历

如果要模拟字典 遍历一定用pairs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = {["name"]="wqx",["age"]=14,["1"]=5}
for k,v in pairs(a) do
--可以传多个参数一样可以打印出来
print(k,v)
end

for k in pairs(a) do
print(l)
print(a[k])
end

--下划线也能表示,也是表示键
for _,v in pairs(a) do -- _ 表示键不想用但是还是能传键
--可以传多个参数一样可以打印出来
print(k,v)
end

九、用table表示类和结构体

Lua中默认是没有面向对象的 需要我们自己实现

1. 表示类

成员变量 成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Student = {
--年龄
age = 1,
--性别
sex = true,
--成长函数
Up = function()
--这样写 这个age 和表中的age没有任何关系 它是一个全局的变量
--print(age)
--想要在内部函数中 调用表本身的属性或者方法
--一定要指定是谁的 表名.属性 或 表名.方法
print(Student.age)
print("长大了")
end,
Learn =function(t)
--第二张 能够在函数内部调用自己属性或者方法的 方法
--把自己作为一个参数传进来 在内部访问
print(t.sex)
print("好好学习")
end
}

C#要是使用类 实例化对象new 静态直接点

Lua中类的表现更像一个类中 有很多静态变量和函数

1
2
3
4
5
6
print(Student.age)
Student.Up()
[[
1
长大了
]]

表外申明 在表外可以继续申明表中变量

1
2
3
4
5
6
7
8
Student.name= "wqx"
Student.Speak = function()
print("说话")
end

function Student.Speak2()
print("说话2")
end

2. Lua中 . 和:的区别(面试)

点调用函数,需要传什么参数就传什么

冒号调用函数 会默认把调用者 作为第一个变量

1
2
Student.Learn(Student)
Student:Learn()

lua中有一个关键字 self 表示 默认传入的第一个参数

1
2
3
4
function Student:Speak2()
print(self.name)
print("说话2")
end

十、表的公共操作

表中 table提供的一些公共方法的讲解

我们先声明一张表

1
2
t1 = {{age=1, name = "123"},{age = 2 ,name="345"}}
t2 = {name = "wqx",sex= true}

1. 插入函数

table.insert(被插的表,要插的表)

1
2
3
4
5
6
print(#t1)
table.insert(t1,t2) --将t2插入t1
print(#t1)

--2
--3

2.删除指定元素

remove 方法 传表进去 会移除最后一个索引内容

1
2
3
table.remove(t1)
print(#t1)
print(t1[1].name)

remove 方法 传两个参数

第一个参数 是要移除内容的表

第二个参数 是要移除内容的索引

1
2
table.remove(t1,1)
print(t1[1].name)

3. 排序函数

可以将表中数字排序

1
2
3
4
5
6
7
8
t2 = {52795}
table.sort(t2)
--默认为升序排列
for _,v in pairs(t2) do
print(v)
end
--2 5 5 7 9

传入两个参数 第一个是用于排列的表

第二个是 排序规则函数

1
2
3
4
5
6
7
8
9
table.sort(t2,function(a,b)
if a > b then
return true
end
end)
--降序
for _,v in pairs(t2) do
print(v)
end
  • 如果 a > b,则 a 排在 b 前面(降序)。
  • 如果 a <= b,则 a 排在 b 后面(不返回 true,默认返回 nil)。

4.拼接函数

table.concat(分割元素的分隔符) 返回一个string

用于拼接表中元素 返回值 是字符串

1
2
tb = {"123","456","789","10101"}
str = table.concat(tb,";")

十一、特殊用法:多变量赋值 三目运算符

1. 多变量赋值

多变量赋值,如果后面的值不够,会自动补空。

后面值多了,会自动省略。

1
2
3
4
5
6
7
a,b,c = 1,2,"123"
print(a)
print(b)
print(c)
-1
-2
-123

2.多返回值

规则同上

1
2
3
4
5
6
7
8
9
10
11
12
13
function Test()
return 10,20,30,40
end

a,b,c = Test()
print(a)
print(b)
print(c)
[[
10
20
30
]]

3. and or

and or 它们不仅可以连接 boolean 任何东西 都可以用来连接。

在lua中 只有 nil和 false 才认为是假

并且支持短路

4.模拟三目运算符

? : 公式: (x>y) and x or y

1
2
3
4
5
6
7
8
9
10
11
12
x=3
y=2
local res = (x>y) and x or y
print(res)
--x=3 y=2
--(x>y) and x -> x 同真则真,要计算到x
--x or y ->x 有真则真,返回x

--x=1 y=2
--(x>y) and x -> x>y 有假则假 短路
--x>y or y ->x 有真则真,返回y

十二、多脚本执行

1.全局变量和本地变量

lua脚本中所有不加local声明的变量都是全局变量

1
2
3
4
5
6
for i=1,2 do
c="123"
end

print(c) --在其他语言中c已经被释放了,但是这里是全局变量,出了代码块仍然能执行
--123

本地(局部)变量关键字 local

1
2
3
4
for i = 1,2 do
local d="123"
end
print(d) --nil

2. 多脚本执行

关键字 require(“脚本名”)

也是从上往下依次执行

  • 注1:只要是执行过的脚本,全局变量都可以互相使用。本地变量则不行。
  • 注2:require加载执行的脚本 加载一次过后不会在被执行

如要使用其他脚本的 本地变量 可以使用return 返回一个外部的变量

image-20250506154104244

image-20250506154206316

3. 脚本卸载

package.loaded[“脚本名”] 可以得到脚本有没有被执行过。

返回值是boolean 意思是 该脚本是否被执行

1
2
3
--卸载已经执行过的脚本
package.loaded["Test"] =nil

3.大G表

G是一个总表(table) 他将我们申明的所有全局的变量都存储在其中. 下划线大G

1
2
3
for k,v in pairs(_G) do
print(k,v)
end

image-20250506153532906

本地变量 加了local的变量是不会存到大_G表中的_

十三、协程Coroutine

1.协程的创建

第一种(常用):coroutine.create()

1
2
3
4
5
6
fun =function()
print(123)
end

co = coroutine.create(fun)
print(co)

image-20250506154715384

协程的本质是一个线程对象

第二种:coroutine.wrap()

1
2
3
4
5
6
fun =function()
print(123)
end

co2 = coroutine.wrap(fun)
print(co2)

第二种创建出来的协程是一个函数

2. 协程的运行

第一种:coroutine.resume(协程对象)

对应 create 创建的

1
2
coroutine.resume(co)
--123

第二种:直接调函数即可

1
co2()

3. 协程的挂起

coroutine.yield()

yield可以带返回值回来

默认 第一个返回值 为是否启动成功

第二个为 yield里面的返回值

wrap会直接返回值,不会返回boolean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun2 = function()
local i =1
while true do
print(i)
i= i+1
--挂起
coroutine.yield(i)
end
end

co3= coroutine.create(fun2)
a,b=coroutine.resume(co3)--true 1
coroutine.resume(co3)
[[ 结果:
1
2
]]

--第二种
co4 = coroutine.create(fun2)
co4()
co4()

4.协程的状态

coroutine.status(协程对象) 返回string

dead 结束

suspended 暂停

running 进行中

coroutine.running() 可得到运行的协程线程号

image-20250506161241436

十四、元表

1. 元表概念

任何 表变量 都可以作为 另一个表变量的元表

任何 表变量 都可以有自己的元表 (爸爸)

当我们子表中进行一些特定操作时,会执行元表中的内容。

2. 设置元表

setmetatable(mtTable,meta)

第一个参数 子表

第二个参数 元表(爸爸)

1
2
3
4
meta = {}
mtTable = {}
setmetatable(mtTable,meta)
--mytable = setmetatable({},{}) 可以写成一行

3. 特定操作—___tostring

当子表要当作字符串使用时,默认调用元表中的tostring方法

1
2
3
4
5
6
7
8
9
meta2 = {
__tostring = function()
return "wqx"
end
}
mtTable2 = {}
setmetatable(mtTable2,meta2)
print(mtTable2)
--wqx

4. 特定操作— __call

当子表被当作函数时使用,就会默认调用__call

当希望传参数时 一定要记住 默认传第一个参数 是调用者自己

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
meta3 = {
__tostring = function()
return "wqx"
end,
---------------------
__call = function(a,b)
print(a.name)
print
print("123")
end
}
mtTable3 = { name = "wqx250"}
setmetatable(mtTable3,meta3)

--把子表当作函数使用 就会调用__call
mtTable3(1)
--wqx250
--1
--123

5. 特定操作—运算符重载

可以在元表中特定一些函数来进行操作

+: __add

— :__sub

*: mul

/ : dov

% : mod

^ : pow

== : eq 注:如果要用条件运算符 来比较 。这两个对象的元表一定要一致 才能准确调用方法。

< : lt

<= :le

.. : concat

1
2
3
4
5
6
7
8
9
meta4 = { --要加的左右元表
--相当于运算符重构 当子表使用 +运算符时调用
__add = function(t1, t2)
return 5
}
myTable4 = {}
setmetatable(myTable4,meta4)
myTable5 = {}
print(myTable4 + myTable5)

6. 特定操作—__ index 和__newindex

__当index 当子表中 找不到某一属性

会到元表__index指定的表取找索引

如果__ index 和__ newindex是函数 那就调用这个函数

1
2
3
4
5
6
7
8
9
10
meta6 = {
age = 1,
-- __index = meta6 -- ❌ 此处meta6尚未完全初始,写在这里输出是nil,建议把__index写在表外
}
meta6.__index = meta6
myTable6 = {}
setmetatable(myTable6,meta6)

print(myTable6.age)
--1

index可以一层一层往上找

1
2
3
4
5
6
meta6Father = {
age = 1
}
meta6Father.__index = meta6Father
setmetatable(meta6, meta6Father)
--这时这个age ,myTable6没有会找meta6,meta6没有会找他爸

newIndex 当赋值时 ,如果赋值一个不存在的表

那么会把这个值赋值到元表中newindex指定的表中,不会修改自己

1
2
3
4
5
6
7
meta7 = {}
meta7.__newindex = {}
myTable7 = {}
setmetatable(myTable7,meta7)
myTable7.age =1
print(meta7.__newindex.age)
--1

getmetatable(myTable6) 得到元表

1
print(getmetatable(myTable6))

rawget 当我们使用它时 即使我们设置了元表和index ,只会去找自己身上有没有这个变量

1
print(rawget(myTable6,"age"))

rawset 该方法 会忽略newindex 的设置 只会改自己的变量

1
rawset(mtTable7,"age",2)

十五、面向对象——封装、继承、多态

1. 封装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Object = {}
Object.id = 1
function Object:Test()
print(self.id)
end

function Object:new()--构造函数
--self默认第一个传参 是自己 就是Object
local obj={}
self.__index =self --变量找不到就去Object找 啥都能找
setmetatable(obj,self)
return obj
end


--对象就是变量 返回一个新的对象
--返回出去的内容实际上 是表对象
--返回的是空表 是因为设置了元表才能访问
local myObj= Object:new()
print(myObj)
print(myObj.id)

print(myObj:Test())

--对空表中 申明一个新的属性 叫做id
myObj.id =2
myObj:Test() --这时myObj已经有id了 而传入的就就是myObj 这时不会去找Object的id
[[
table: 0x85c740
1
1
2
]]

2. 继承

写一个用于继承的方法 用__G表进行表的创建

1
2
3
4
5
6
7
8
9
10
11
12
function Object:subClass(className)
_G[className] = {} --在全局声明一个表
--继承规则用元表 没有就去找元表
local obj = _G[className]
self.__index = self
--子类定义一个 base属性 代表父类
obj.base = self 把base指向父类
setmetatable(obj,self)
end
Object:subClass("Person") --Person继承Object
print(Person.id)
--1

实例化 对象逻辑

1
2
local p1 = Person:new()
print(p1.id)

image-20250506224652147

3. 多态

相同行为 不同表象 就是多态

相同方法 不同执行逻辑 就是多态

image-20250506232411619

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Object:subClass("GameObject")
GameObject.posX= 0;
GameObject.posY= 0;
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
print(self.posX)
print(self.posY)
end

GameObject:subClass("Player")
function Player:Move()
--base.Move() 如何保留父类逻辑
--在继承函数中 声明一个base代表父类 GameObj表
--避免把基类表 传入到方法中 这样相当于共用表属性
--我们如果想执行父类逻辑 不要用冒号
--要通过点调用 然后传入自己第一个参数
self.base.Move(self)


end

local p1 = Player:new()
p1:Move()

--目前这种写法有坑 不同对象使用的成员变量是相同的不是自己的
local p2= Player:new()
p2:Move()--打印不是11 而是 22
--其实是 self.base:Move() 只是调用了GameObject自己 ,