Lua语言基础

100 阅读17分钟

一.变量

在lua中所有的变量申明,都不需要申明变量类型,会根据数值自动判断类型

1. 简单变量类型

(1) nil 类似于C#中的null,nil即表示空也是一种类型
可以使用不申明的变量,打印出来就为nil;使用type(a)可以判断a的变量类型

 a = nil
 print(a)
 print(b)   --可以使用不申明的变量,打印出来就为nil
 print(type(a))
 print(type(type(a))) -- 判断type的变量类型

(2) number lua中所有的数值都属于number
无论是小数或者整数,只要是数值在lua中都被认为是number类型

num1=1 
num2=5.9
print(num1)
print(num2)
print(type(num1))

(3) string    其它语言中的char和string都属于string类型

str1 = "123"
str2 = '你'
print(str1)
print(str2)
print(type(str1))

(4) boolean  和其他语言的布尔值一样

bool = false
print(bool)
print(type(bool))

(5) 多变量赋值  数值不匹配则根据多删少补空来处理

local a,b,c = 1,true,"21252"
print(a)
print(b)
print(c)

2. 字符串

在lua中也包含许多关于字符串的方法,方便字符串的使用

  • **( #string } 计算字符串长度 (其中每个汉字占3个长度) **
str = "aBCdeFG你好1"
print(#str) -- 14
  • { [[]] }字符串多行打印 lua中同样可以支持转义字符\n
print("123\n456")

s=[[
你好
我出门了
你呢
]]
print(s)

  • { .. } 字符串拼接 也可以使用C语言中的format
print("123" .. "456")
print(000 .. 111)

-- %d:数字占位符  %a:任何字符占位符  %s:字符占位符
print(string.format("我%d岁",10))  

  • 其他类型转化为字符串tostring()
data = 555
print(tostring(data))
  • 字符串的公共方法 (和其他语言基本一样)
str = "aaaBBBcccDD"

print(string.upper(str))  -- 转化大写
print(string.lower(str))  -- 转化小写

print(string.reverse(str))  --字符串倒置
print(string.find(str,"Bcc"))  --子字符串查找。(6  8) 会打印出子符串的开始和结尾索引
print(string.sub(str,3))  --字符串截取,截取当前索引以及之后的字符串
print(string.sub(str,5))  --字符串截取,截取索引范围内的字符串

print(string.rep(str,2))  --字符串重复,根据后面参数重复当前字符串
print(string.gsub(str,"B","*"))  --修改字符串,会打印修改后的字符串和修改次数

a = string.byte("Lua",1)    --字符转ASCII码,参数指定字符的索引

print(a)  
print(string.char(a))    --ASCII码转字符

3. 运算符

在lua语言中的运算符存在与其他语言不同的地方:

  • 没有自增自减和复合运算符
  • 不支持位运算符
  • 不支持三目运算符
  • 算数运算符
    在字符串中的数值计算会自动转换为number类型再打印出来
print("加法运算".. 1 + 2)
print("123.4" + 21)
print("123.4" - 1)
print( "取余运算".."123" % 2)
print( "幂运算".."2" ^ 2)
  • 条件运算符 > < == <= >= ~=不等于
print(3 >= 1)
print(3 ~= 1) --不等于
  • 逻辑运算符 and与 or或 非not 支持短路判断
print(true and false)
print(true or false)
print(not false)
  • and和or的特殊使用
    不仅可以使用布尔值来连接判断。在lua中只有nil和false才为假
--根据短路原则,and需要判定到后一个值才能确实布尔值,输出为最后判断的值
print(2 and 1)  -- 判断2为true继续判断,所以输出为1  
print(2 or 1)
local x,y = 10,25
local result = (x>y) and x or y;
print(result) -- 25

4. 复杂数据类型

(1) 函数 function
(2) 表 table
(3) 数据结构 userdata
(4) 协同程序 thread(线程)

二.选择_循环语句

在lua中同样存在一些选择循环语句,但格式往往有些不同。并且在lua中并不存在switch循环语句。

1. 条件分支语句

  • 单分支
    使用if...then...的形式来进行判断分支语句
a= 5
b= 10
-- 支持and,or
if a >5 and b<20 then
    print(a)
end

if a >5 or b<20 then
    print(a)
end
  • 多分支
    使用 if ... then ... elseif ... then ... else ... end的形式来进行多判断分支语句
if a >5 and b<20 then
    print(a)
elseif a>6 then     -- 多分支的elseif中间不需要空格
    print(b)
else 
    print("以上都不满足")
end

2. 循环

  • while循环
    使用 while ... do ... end 的形式来进行循环语句

num =1;
while num<5 do
    print("当前num等于".. num)
    num = num +1
end
  • do..while循环
    使用 repeat ... until ... 的形式来进行循环语句

repeat  --进入循环 类似do
    print("当前x等于".. x)
    x = x -1
until x < 2     --跳出循环的条件 类似while(x<2)
  • for循环
    使用 for ... do ... end 的形式来进行循环语句,不指定的话默认递增

for i =1,5 do
    print("i="..i)
end

-- 第三个参数表示步长,默认为1,负数表示递减
for a =10,5,-1 do
    print("a="..a)
end
  • 迭代器遍历
    (1) ipairs迭代器
    类似#arr遍历,只能找到连续索引的键值,不连续索引无法遍历 ,需要给定两个遍历来获取索引和对应的值

a={[0] =1,2,3,[4] = 10,[-1] =20}

-- ipairs迭代器
for i,k in ipairs(a) do
    print("索引:"..i..",值:"..k)
end

(1) pairs迭代器 无论任何索引都可以遍历出来,需要给定两个遍历来获取索引和对应的值


a={[0] =1,2,3,[4] = 10,[-1] =20}

-- ipairs迭代器
for i,k in pairs(a) do
    print("索引:"..i..",值:"..k)
end

三.函数

函数方法必须写在调用前面,否则调用找不到函数方法

1. 函数定义
function 方法名()...end 或者 方法名=function()...end 中定义方法名和方法体。

-- 第一种定义函数
function newMethod()
    print("newMethod函数方法")
end

-- 第二种定义函数
method = function()
    print("带函数的变量method")
end

2. 无参无返回值的函数

function func()
    print("func函数方法")
end

func1 =function()
    print("func1函数方法")
end

--调用方法
func()
func1()

3. 有参无返回的函数
function 方法名(参数)...end 和其他语言一样,参数写在函数括号中

function funcPara(str)
    print(str)
end

funcPara(10)
funcPara()  --调用有参函数不给参数默认给空nil
funcPara(2,3)     -- 参数不符合但只会匹配对应参数,多余的舍弃

4. 有返回值有参的函数

  1. 直接在方法体内添加return
  2. return可以同时返回出多个变量,但必须使用多个变量来获取,否则直接抛弃

function funcReturn(str,a)
    return str,a
end

function funcReturn1(str)
    return str,true
end

-- 多返回值可以通过多个变量来承接多个返回值
t1,t2 = funcReturn1(5)
print(t1)
print(t2)

5. 函数类型
函数类型都是function


funcType = function()
    print(1111)
end

print(type(funcType))   --函数类型都是function

6. 函数重载
lua不支持函数重载,如果强制调用重载函数,他会默认调用最后一个重载函数

7. 变长参数
参数中为三个点,则参数是可变的,会使用一个表存起来,在使用。调用时候自己添加自定义个参数


function trans(...)
    arr = {...}
    for i = 1,#arr do
        print(arr[i])
    end
end

trans("111",22,true,12.2,'ddd')

8. 函数嵌套
在一个方法内定义另一个或多个方法


function out()
    --直接返回函数方法
    return function()
        print("嵌套函数in")
    end
end

-- 函数也算是一种变量,所以也可以使用参数来获取和传递
test = out()
test()
  • 函数嵌套中的闭包问题

闭包是一个函数和其相关引用环境的组合体。 闭包可以将函数内部的变量保持在内存中,并在函数内部和外部之间共享这些变量。


function bao1(num1)
    --此时num1参数生命周期发生变化,会继续将num1给到返回函数中
    return function(num2)
        return num1 + num2
    end
end

bao2 = bao1(2)
print(bao2(3)) --5

四. 表table

在lua语言中,其实所有的复杂类型统称为表

1.Array数组

1. 数组array
在lua中的数组是可以有多种类型的,不指定具体类型,并且索引都是从1开始


-- 数组并不指定一个类型,可以任意类型
arr = {1,20,5,nil,'a',"abc",true}
print(arr[1])

(1). 数组的遍历

  • 可以使用for循环,#arr表示数组长度,数组中存在nil同样会正常打印出来
  • lua5之前的版本,当数组中存在nil会忽略以及之后的元素

arr = {1,20,5,nil,'a',"abc",true}

for i=1,#arr do
    print(arr[i])
end

2.二维数组


arr = {{1,2,3},{'1','2','3'},{true,false,false}} 

print(arr[2][3])
end

(1).二维数组的遍历
双for循环或使用迭代器遍历

arr = {{1,2,3},{'1','2','3'},{true,false,false}} 

for i=1,#arr do
    b = arr[i]
    for j=1,#b do
        print(b[j])
    end
end

3.自定义索引
可以指定某个值的索引,可以是负数索引,没有指定的值依旧按照索引为1开始


newarray = {[0] = 10,[10]=20,30,[-10]=40,50}    
print(newarray[1])
--修改了索引的数值不计算到表的长度
print(#newarray) -- 结果为2

2.字典

  • 字典
    在lua中的字典和数组基本类似,对于一个键值对表示:[键]=值

a = {["1001"]=10,["1002"]=true,["name"]=30,["1004"]='A',["1005"]=50}

print(a["1001"])
--可以通过字典.键的形式获取对应的值,但是键中不能是纯数字 print(a.1001)
print(a.name)

-- 修改键值对
a.name = "更改"
print(a.name)

-- 修改键值对
a.sex = "男"
print(a.sex)
  • 遍历字典

a = {["1001"]=10,["1002"]=true,["name"]=30,["1004"]='A',["1005"]=50}

for k,v in pairs(a) do
    print(k,v)
end

--只获得值,使用_下划线不给定变量输出只打印值参数
for _,v in pairs(a) do
    print(v)
end

3.类与结构体

lua中没有面向对象的实现,类无法通过new去创建对象

  • 类与结构体定义和使用 可以定义字段和方法,每个变量后都需要用逗号分开

Student = {
    name = "卷卷",
    age,
    Func = function()
        print(Student.age)  --当函数内需要访问类中的变量,必须指定类名.变量
        print("牛逼")
    end,
    
    Learn = function(stu)
        print(stu.name)
        print("学习方法")
    end,
}

--调用类中的变量或函数
Student.age = 5
print(Student.age)
Student.Func()
-- 使用冒号调用方法会自动将调用者作为方法的参数
-- 而使用点调用则必须要填入参数否则报错
Student:Learn() 
-- 只能在外部声明方法的时候可以使用冒号,在内部不可以使用冒号调用
function Student:Talk()
    print(self.name.."正在说话")    --self表示默认传入的第一个参数
end

Student:Talk() 

4.表的公共操作

 
t1 = {{name = "卷卷",age = 18},{name = "哈哈",age = 21}}
t2 = {name = "嘿嘿",age = 15}

print(#t1)   -- 表内数据量
--插入方法
table.insert(t1,t2)
print(t1[3].age)

--移除方法,默认是移除最后一个元素表,后面参数表示要移除的索引值对应的内容
table.remove(t1,1)
print(t1[1].name)

--排序,对表内数据排序,默认为升序排序
t2 = {10,50,30,580,45,32,80,674}
table.sort(t2) 
for i=1,#t2 do
    print(t2[i])
end

--降序排列  第二个参数为排序规则函数
table.sort(t2, function(a,b)
    if a>b then
        return true
    end
end) 

for i=1,#t2 do
    print(t2[i])
end

-- 字符串拼接
strtable = {"你好","在忙","睡了"}
str = table.concat(strtable,"--")    --连接函数,用来拼接表中元素,返回值为一个字符串
print(str)

五. 多脚本执行

在多个lua脚本之间进行数据获取和交互

1.全局变量和本地变量


--全局变量
a = 1
b = true
--本地变量
local str = "本地变量"

for i =1,2 do
    local num = "循环"
    print(num)
end
print(num) -- 无法拿到本地变量,输出为nil

2.多脚本执行
通过内置的包路径去查询lua脚本文件,再进行访问数据

  • user.lua脚本

-- 可以添加相对位置的包路径到内置包路径中
package.path = "luaRun/?.lua;"..package.path

--根据package.path的路径寻找这个名字的脚本,脚本只加载一次
require("Test")
-- 访问Text.lua中的变量
print(squre1)
print(currentlocal)

-- 要获取外部脚本的本地变量,要使用本地变量来获取,并且外部脚本已经return出本地变量
local testlocal = require("Test")   --拿到返回出来的本地变量
print(testlocal)
  • Test.lua脚本

print("加载Test脚本")
-- 在该脚本中放置全局变量和本地变量
squre1 = "全局"
local currentlocal = "本地"

return currentlocal     --可以将本地变量返回出去给其他脚本使用

3.多脚本执行的脚本卸载
通过内置的包路径去查询lua脚本文件,再进行访问数据


-- 获取当前脚本是否被加载过
print(package.loaded["Test"])   --返回布尔值,是否加载。如果脚本中有返回值则打印返回值
--卸载加载的脚本
package.loaded["Test"] = nil
print(package.loaded["Test"])   --打印为nil,而不是false

4.大G表

_G表是一个总表,他将所有申明出来的全局变量存储到这个大G表中


--遍历所有大G表中的全局变量,而本地变量并不存在于大G表中
for k,v in pairs(_G) do
    print(k,v) 
end

六. 协同程序(协程)

在多个lua脚本之间进行数据获取和交互

1.协程的创建


--使用 coroutine.create() 创建线程类型的协程
local create1 = function()
    print("coroutine.create()创建协程")
end

coc = coroutine.create(create1)
print(coc)   --会打印出来协程的地址,并且类型属于线程thread对象

--使用 coroutine.wrap()创建函数类型的协程
local wrap1 = function()
    print("coroutine.wrap()创建协程")
end

cop = coroutine.wrap(wrap1)
print(cop)  --会打印出来协程的地址,但是类型属于函数类型

1.协程的创建

  • 使用 coroutine.create() 创建线程类型的协程类型属于thread对象
  • 使用 coroutine.wrap() 创建函数类型的协程类型属于函数类型

--使用 coroutine.create() 创建线程类型的协程
local create1 = function()
    print("coroutine.create()创建协程")
end

coc = coroutine.create(create1)
print(coc)   --会打印出来协程的地址,并且类型属于线程thread对象

--使用 coroutine.wrap()创建函数类型的协程
local wrap1 = function()
    print("coroutine.wrap()创建协程")
end

cop = coroutine.wrap(wrap1)
print(cop)  --会打印出来协程的地址,但是类型属于函数类型

2.协程方法的调用

  • coroutine.create()创建的协程使用coroutine.resume()调用协程
  • coroutine.wrap()创建的协程可以当作函数直接调用即可

--匹配coroutine.create()创建的协程使用coroutine.resume()
coroutine.resume(coc)
--匹配coroutine.wrap()创建的协程可以当作函数直接调用即可
cop()

3.协程的挂起

  • 使用coroutine.yield()挂起协程

-- 使用coroutine.yield()挂起协程
local create2 = function()
    local i = 1
    while true do 
        print("循环"..i)
        i= i+1

        coroutine.yield(i)
    end
end

cocs1 = coroutine.create(create2)
coroutine.resume(cocs1)
coroutine.resume(cocs1)  --执行几次就会循环几次

temp1,temp2 = coroutine.resume(cocs1)
print(temp1,temp2)     --默认会有返回值,第一个为是否成功执行,第二个为当前协程挂起中的值
cop()

cocs2 = coroutine.wrap(create2)
cocs2()     --create和wrap分别执行属于不同的线程上

4.协程的状态

  • dead协程已经停止,suspended协程被暂时挂起,running 协程正在运行
  • coroutine.status(协程对象) 查看一个协程处于的状态
  • coroutine.running() 如果在外部调用,协程已经结束或者暂停了所以总是nil

print(coroutine.status(cocs1))

coroutine.running()   获取当前正在运行的协程的编号

七. 元表

  • 所有的表的变量都可以作为另一个表变量的元表
  • 任何表变量都可以有自己的元表
  • 拥有元表的表变量在进行一些操作的时候都会执行元表的内容

1.元表的设置

  • setmatatable(子表,元表)给子表设置一个元表,仅仅是设置,无其他操作

table = {}
mytable = {}
setmetatable(mytable,table)

2.元表的操作
(1) __tostring方法
当子表输出字符串的时候会默认调用__tostring方法,并且可以自动传入参数


table1 = {
    __tostring = function()
        return "元表的__tostring"
    end
}
mytable1 = {}
setmetatable(mytable1,table1)

print(mytable1) -- 元表的__tostring

(2) __call方法
当子表当作函数使用的时候会默认调用__call方法,并且可以自动传入参数


table2 = {
    __tostring = function()
        return "元表的__tostring"
    end,

    __call = function(x,y)
        --函数中的两个参数,第一个默认为调用者自身,第二个才是调用传进来的参数
        print(x)
        print(y)
        print("元表的__call")
    end
}
mytable2 = {}
setmetatable(mytable2,table2)

mytable2(1)

(3) 运算符重载方法
根据特定的函数名称可以对运算符进行重载,根据自己的运算规则给运算符指定新的规则


table3 = {
    --add重载了+运算符,将相加的逻辑写在方法里,然后可以直接使用+
    __add = function(t1,t2)
        return t1.age + t2.age
    end,

    --sub重载了-运算符,将相减的逻辑写在方法里,然后可以直接使用+
    __sub = function(t1,t2)
        return t1.age - t2.age
    end,

    --乘法
    __mul = function(t1,t2)  
    end,

    --除法
    __div = function(t1,t2)
    end,

    --次方
    __pow = function(t1,t2)
    end,

    --等于
    __eq = function(t1,t2)
    end,

    --小于
    __lt = function(t1,t2)
    end,

    --小于等于
    __le = function(t1,t2)
    end,

    --连接符..
    __concat = function(t1,t2)
    end
}
mytable3 = {age = 10}
mytableclone = {age = 15}
setmetatable(mytable3,table3)

print(mytable3 + mytableclone) 
print(mytable3 - mytableclone) 

(4) __index方法
如果当前索引在子表中找不到索引对应的值,那么会去找__index所指定的表查找


table4 = {
    age = 40,
}
mytable4 = {}
--__index所指定表,必须指定才能找到,否则返回nil
table4.__index = table4 
setmetatable(mytable4,table4)
print(mytable4.age)

--rawget()从自身表中获取是否存在属性
print(rawget(mytable4,"age")) --没有这个属性返回nil

(5) __newIndex方法
当赋值的时候,如果赋值的索引为一个不存在的索引,那么这个值就会去__newindex所指的表赋值


table5 = {
    age = 40
}
mytable5 = {}
table5.__newindex = table5
setmetatable(mytable5,table5)
mytable5.name = "卷"
print(mytable5.age) --不修改本身的表,所以打印为nil
print(table5.age)
print(table5.name)

--rawset()从自身表中设置属性
rawset(mytable5,"age",2)
print(mytable5.age)

八. 自带库

提供一些api方法,和其他语言一样,如mathf库 1.时间


print(os.time())    --查看系统时间
print(os.time({year= 2023,month= 9,day = 14}))    --自己传入时间
local nowTime = os.date("*t")   --获取当前时间,精确到秒
for k,v in pairs(nowTime) do
    print(k,v)
end
print(nowTime.sec)  --根据对象打印出指定时间

2.数学运算


print(math.abs(-11))    --绝对值
print(math.deg(math.pi))   --弧度转角度
--三角函数
print(math.cos(math.pi))
print(math.sin(math.pi))

--向上向下取整
print(math.ceil(3.4))
print(math.floor(3.4))

--最大值最小值
print(math.max(5,6))
print(math.min(5,6))  

--小数的小数位分离
print(math.modf(6.8))  --6,0.8

--随机数    lua中的随机数必须要根据种子变化才会发生变化,种子树不变那不会随机
math.randomseed(os.time())
print(math.random(100))
print(math.random(100))

九. 面向对象

在lua语言中并不存在面向对象的概念,但是可以实现封装、继承、多态的特征。面向对象的类都是基于table来实现 1.封装


-- 定义一个类,id和Test方法
Object = {}
Object.id = 10
function Object:Test()
    print("Test方法调用")
    print(self.id)
end

-- 实现创建一个实例对象的方法
function Object:new()
    local obj = {}
    --需要使用到__index,当当前表中无值的时候就需要访问index指定的表
    self.__index = self
    setmetatable(obj,self)
    return obj
end

--调用new方法,模拟实例化对象
local myobj = Object:new()
print(myobj.id)
myobj:Test()

-- 在新表中声明了id属性,那么调用的时候就是使用自己的表的id,不再是查找元表的id
myobj.id = 99 
myobj:Test()

2.继承


function Object:subClass(className)
    --使用大G表来添加类
    _G[className] = {}
    --写入继承需要的规则
    local obj = _G[className]
    self.__index = self
    --创建一个base方法,让子类在重写父类方法的时候可以让父类的行为也保留下来
    obj.base = self
    setmetatable(obj,self)
end

-- 调用方法相当于继承了Object父类
Object:subClass("Person")
print(Person.id)

3.多态
相同的行为具有不同的表现


Object:subClass("GameObject")

GameObject.posX = 6
GameObject.posY = 8
function GameObject:Move()
    self.posX = self.posX +1
    self.posY = self.posY +1
    print(self.posX)
    print(self.posY)
end

GameObject:subClass("Student")
function Student:Move()
    --相当于将父类表作为第一个参数传入方法中,应该避免父类表传入base方法
    -- self.base:Move() 错误调用,相当于将父类表作为第一个参数传入方法中,不可以带入错误
    self.base.Move(self) --正确写法
end

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

local p2 = Student:new()
p2:Move()