此文章部分内容和思维导图来源于唐老狮相关Lua课程,通过Vistual Studio Code语法测试,如有问题,请在以下留言

由于全部为学习格整理而成,故本网站对本文章拥有相关创作权,因此在搬运或者转载二次改变前请与本人联系,禁止一切未经允许的搬运行为,若出现或发现侵权行为,本人有权对相关侵权行为进行举报,维护本人和合法权益。

一.Lua相关注释

这里需要注意的是在C#中单行注释是使用”//“,多行注释是”/%内容%/“,在lua中的单行注释和多行注释相关格式如下图

--单行注释
--[[
多行注释
]]

二.数据类型

写在最前面

  • 在lua中无需定义数据的类型,在使用中会自动识别

  • lua当中的基本数据类型:

    (1)nil(类似于c#中null)

    (2)number(所有的数值都是number==>number包括int float double)

    (3)string (lua中没有char,字符串的声明使用单引号或者双引号包裹)

    (4)boolean(返回的是true或false)

  • lua当中的复杂数据类型:

    (1)函数(function)

    (2)表(table)【表需要特别学习,特别的重要】

    (3)数据结构(userdata)

    (4)协同程序(thread(线程))

  • lua中带 所有的变量申明,都不需要申明变量类型,同理可以随便赋值,可以自动判断类型==>类似c#中的var

  • lua中使用没有声明过的变量并不会报错,默认返回的是nil

  • lua默认是没有 面向对象的 ,需要自己实现

  • 在lua中的”数组” 起始位置与c#不同,从1开始

  • 当对变量多赋值时,会将后面多赋的值自动省略

  • 当对变量少赋值时,会将后面不够的值直接自动赋为空值
  • 多返回值时,用几个变量接就会有多少个值
  • 如果变量数大于方法返回的值数量,则会进行补空处理

  • 如果 变量数小于方法返回的值数量,则会根据变量数量进行接取

1.基本数据类型

为了方便阅读和页面美观,请点击下拉框进行阅读

nil的使用

a=nil  --定义a这个变量的值为nil
print(a) --这里是输出a这个变量,输出值也同样是nil,不过输出的是字符串类型

number(数值类型)

  • 这里需要注意一点:在上面赋过值得变量,可以重复使用,在下面依然可以重新赋值,也同样会根据相关赋值的类型转化为相对应的类型
a=1 b=1.25
print(a) --输出的值是1
print(b) --输出的值是1.25

string(字符串类型)

  • 在lua中是没有字符的,在定义字符串时可以使用以下三种方式:
    (1)单引号 'SeveneStudio'
    (2)双引号 "SeveneStudio"
    (3)中括号 [[SeveneStudio]]
a="SeveneStudio_Logo" 
print(a)  --输出的值是SeveneStudio_Logo
a='SeveneStudio——Company'
print(a)  --输出的值是SeveneStudio——Company
a=[[SevenStuido_KaiFa]]
print(a)  --输出的值是SevenStuido_KaiFa

boolean(Boolean类型)

  • 在lua中只有true和false代表真与假,这个与c#有着区别
a=true
print(a) -- 输出的值是true
a=false
print(a) -- 输出的值是false

2.type()的使用和字符串扩展

type() 获取变量类型

  • 通过type()可以获取变量类型==>返回值是变量的数据类型,属于string类型
a=123
print(type(a))  --因为a的类型是number,所以通过type()获取的输出的是number
a="123"
print(type(a))  --因为a的类型是string,所以通过type()获取的输出的是string

3.字符串的相关使用

(1).字符串长度的计算

计算字符串的长度

相关方法:print(#变量名)

注意点
1.一个汉字占3个长度
2.英文字符占1个长度
s="aBcDEfG字符串"
print(#s)

(2).字符串的换行

字符串的换行

说明:字符串的换行/字符串多行打印在lua中是与c#一样,是支持转义字符的

相关格式:可以使用\n或者[[字符串]]

print("123\n123")
s=[[
这里
是
SeveneStudio
]]
print(s)

(3).字符串的拼接

字符串的拼接

相关格式:

(1)在lua中字符串的拼接通过..实现,字符串..字符串

print("HaXinXi".."XueYuan")  -- HaXinXiXueYuan
s1="Wang"
s2="Li"
print(s1..s2)   -- HaXinXiXueYuan

(2)string.format()

注意点
%d 与数字进行拼接
%a 与任何字符拼接
%s 与字符配对
print(string.format("我是哈信息%d级学生",18))   --我是哈信息18级学生
print(string.format("我的家乡是%s市","承德"))   --我的家乡是承德市

(4).字符串的转化

字符串的转化

格式:string.upper(字符串)

注意点
string.upper(argument): 字符串全部转为大写字母,
string.lower(argument):字符串全部转为小写字母
s="SeveneStudio"
print(string.upper(s))  --SEVENESTUDIO
print(string.lower(s))  --sevenestudio

(5).字符串的翻转

字符串翻转

格式:string.reverse(arg)

print(string.reverse(s)) --oidutSeneveS

(6).字符串的查找

字符串查找

格式:string.find (str, substr, [init, [plain]])

init 指定了搜索的起始位置,默认为 1,可以一个负数,表示从后往前数的字符个数。
plain 表示是否以正则表达式匹配。

注意点
(1)lua的起始位置与c#不同,从1开始;
(2)string.find ()会返回起始位置和结束位置
s="SeveneStudio"
print(string.find(s,"eve")) --2       4

(7).字符串的截取

字符串截取

格式:string.sub(s, i [, j])

s:要截取的字符串;

i:截取开始位置;

j:截取结束位置,默认为 -1,最后一个字符。

string.sub(s, i [, j])
s="SeveneStudio"
print(string.sub(s,2,6)) --evene

(8).字符串的拷贝

字符串拷贝

格式:string.rep(string, n)

注意点
返回字符串String的n个拷贝,即将字符串复制n个返回
s="SeveneStudio"
print(string.rep(s,3)) --SeveneStudioSeveneStudioSeveneStudio

(9).字符串的替换

字符串替换

格式:string.gsub(mainString,findString,replaceString,num)

mainString 为要操作的字符串;

replaceString 要替换的字符;

findString 为被替换的字符;

num 替换次数(可以忽略,则全部替换);

这里会返回两个:1.替换后的字符串,2.替换次数
s="SeveneStudio"
print(string.gsub(s,"e","*"))  --S*v*n*Studio    3

(10).字符串的字符和ASCII相互转换

字符串的字符和ASCII相互转换

格式:字符转ASCII码:string.byte(string,int)

​ ASCII码转字符:string.char(int)

注意点
string.byte(string,int) 字符转ASCII码 ==>string是指字符串,int是需要转换的位置
string.char(int) ASCII码转字符==>int指需要ASCII码转化为字符
print("字符转ASCII码")
print(string.byte(s,1))  -- 83
print("ASCII码转字符")
print(string.char(string.byte(s,1)))  --S

4.复杂数据类型

  • 函数(function)

  • 表(table)【表需要特别学习,特别的重要】

  • 数据结构(userdata)

  • 协同程序(thread(线程))

注意:

(1)lua函数的使用与c#不同,需要在函数创建后调用

(2)在lua中当你传入的参数和函数中的参数个数不匹配时,并不会报错,而是少于参数个数补空或者多余参数个数丢失

(3)多返回值时,在前面申明多个变量来接取即可,如果变量不够,不会影响结果,值会根据实际接取对应位置的返回值

(4)在lua中并不支持函数的重载,如果函数名相同,参数不同时,则会执行最后声明的函数

基本语法

function 函数名()
end

a=function()
end

1.无参数无返回值的函数

print("**********无参数无返回值**********")
function F1()
    print("f1函数")
end
F1()
F2=function ()
    print("F2函数")
end
F2()

2.有参数无返回值

print("**********有参数无返回值**********")
function F3(a)
    print(a)
end
F3(1)
F3("SeveneStudio")
F3(true)
F3(1,2,3)
相关的测试结果
**********有参数无返回值**********
1
SeveneStudio
true
1

3.有参数有返回值

temp=F4("1")
print("只有一个变量:",temp)
temp,temp2=F4("1")
print("只有两个变量(1):",temp)
print("只有两个变量(1):",temp2)
temp,temp2,temp3=F4("1")
print("有三个变量(1):",temp)
print("有三个变量(2):",temp2)
print("有三个变量(3):",temp3)
相关的测试结果
只有一个变量:   1
只有两个变量(1):      1
只有两个变量(1):      123
有三个变量(1):        1
有三个变量(2):        123
有三个变量(3):        true

4.变长参数

print("**********变长参数**********")
function F7(...)
    --变长参数使用一个表存起来
    --这里需要注意变长参数以...代表
    arg = {...}
    for i = 1, #arg, 1 do
        print(arg[i])
    end    
end
F7(1,"HX",true,4,5,6)

5.嵌套函数

print("**********嵌套函数**********")
function F8()
    return function ()
        print(123)
    end    
end
f9=F8()
f9()

在嵌套函数中引入了一个新的词:闭包【面试常问】

闭包:通过调用含有一个内部函数加上该外部函数持有的外部局部变量(upvalue)的外部函数(就是工厂)产生的一个实例函数

闭包组成:外部函数+外部函数创建的upvalue+内部函数(闭包函数)

function F9(x)
    --改变传入参数的生命周期
    return function (y)
        return x+y
    end
end
f10=F9(10)
print(f10(5))
相关的测试结果

这里最后执行的结果为15==>F9(10)执行的x=10,f(10)执行的是y=5

这里需要注意一个知识点:所有复杂类型本质都是table表

注意:

1.在lua中索引是从1开始的

2.通常在获取长度的时候关键字是#

3.打印长度时,nil(空)在末尾是被忽略的,但是不在末尾而是在某一位置,则会影响获取长度,由于底层的不同,有时转而打印在nil(空)之前的长度,有时会出现计算长度包括nil

1.数组的定义与长度获取

a={1,2,3,"SeveneStudio",true,nil}
print("打印数组第一个值",a[1])
print("a这个数组的长度为",#a)
b={1,nil,"SeveneStudio",true,8,5,6}
print("b这个数组的长度为",#b)
相关的测试结果
打印数组第一个值        1
a这个数组的长度为       5
b这个数组的长度为       7

2.数组的遍历

print("*********数组遍历*********")
for i=1,#a do
    print(a[i])
end
print("---------------------------")
for i=1,#b do
    print(b[i])
end
相关的测试结果
*********数组遍历*********
1
2
3
SeveneStudio
true
---------------------------
1
nil
SeveneStudio
true
8
5
6

3.二维数组的定义

print("*********二维数组*********")
a={{1,2,true},{4,5,"SeveneStudio"}}
print(a[1][3])
print(a[2][3])
相关的测试结果
*********二维数组*********
true
SeveneStudio

4.二维数组的遍历

print("*********二维数组遍历*********")
for i = 1, #a do
    b=a[i]
    for j = 1, #b do
        print(b[j])
    end
end
相关的测试结果
*********二维数组遍历*********
1
2
true
4
5
SeveneStudio

5.自定义索引

注意:

1.计算长度时会忽略小于等于0的索引

2.当对自定义索引进行跳跃性设置时,若只跳跃一个值并不会断掉,长度受最大的索引影响

【迭代器遍历主要是用于遍历表】

  • ipairs遍历

pairs遍历是从1开始往后遍历的,小于等于是不会获取到的
智能找到连续索引的键值,若中间断序,无法遍历后面的内容

a={[0]=1,2,[-1]=3,4,5,[5]=6}
print("***********ipairs迭代器遍历")
--第一种方式
for i, k in ipairs(a) do
    print("ipairs遍历键值"..i.."_"..k)
end
--第二种方式==>可以省略k
for i in ipairs(a) do
    print("ipairs遍历键值"..i)
end
相关的测试结果
***********ipairs迭代器遍历
ipairs遍历键值1_2
ipairs遍历键值2_4
ipairs遍历键值3_5
ipairs遍历键值1
ipairs遍历键值2
ipairs遍历键值3
  • pairs【推荐使用pairs遍历不规则数组

能够把所有的键都可以找到,通过键可以得到值

a={[0]=1,2,[-1]=3,4,5,[5]=6}
print("***********pairs迭代器遍历")
--ipairs遍历是从1开始往后遍历的,小于等于是不会获取到的
--智能找到连续索引的键值,若中间断序,无法遍历后面的内容
for i, k in pairs(a) do
    print("pairs遍历键值"..i.."_"..k)
end
for i in pairs(a) do
    print("pairs遍历键值"..i)
end
相关的测试结果
***********pairs迭代器遍历
pairs遍历键值1_2
pairs遍历键值2_4
pairs遍历键值3_5
pairs遍历键值0_1
pairs遍历键值5_6
pairs遍历键值-1_3
pairs遍历键值1
pairs遍历键值2
pairs遍历键值3
pairs遍历键值0
pairs遍历键值5
pairs遍历键值-1
lua字典的使用与C#相似

1.字典的声明与访问使用

print("****************字典的声明和使用****************")
--字典由键值对构成
a={["id"]=1,["name"]="SeveneStudio",["age"]=22,["hobby"]="踢足球,打篮球"}
--第一种方式访问
print(a["id"])
print(a["name"])
print(a["age"])
print(a["hobby"])
print("--------------------------------------------------")
--第二种方式访问
print(a.id)
print(a.name)
print(a.age)
print(a.hobby)
print("***修改***")
a["name"]="HYH"
print(a["name"])
print(a.name)
print("***增加***")
a["sex"]="man"
print(a["sex"])
print(a.sex)
相关的测试结果
****************字典的声明和使用****************
1
SeveneStudio
22
踢足球,打篮球
--------------------------------------------------
1
SeveneStudio
22
踢足球,打篮球
***修改***
HYH
HYH
***增加***
man
man

2.字典的遍历

print("****************字典的遍历****************")
for k,v in pairs(a) do
    print(k,v)
end
print("****************字典的值遍历****************")
for _,v in pairs(a) do
    print("单个值:",v)
    print("带上键:",_,v)
end
print("****************字典的键遍历****************")
for k in pairs(a) do
    print(k)
    print(a[k])
end
相关的测试结果
****************字典的遍历****************
sex     man
name    HYH
hobby   踢足球,打篮球
age     22
id      1
****************字典的值遍历****************
单个值  man
带上键  sex     man
单个值  HYH
带上键  name    HYH
单个值  踢足球,打篮球
带上键  hobby   踢足球,打篮球
单个值  22
带上键  age     22
单个值  1
带上键  id      1
****************字典的键遍历****************
sex
man
name
HYH
hobby
踢足球,打篮球
age
22
id
1

在开始的时候已经说过了lua没有面向对象的,所以这里通过表类实现类

下面给大家说明一下相关知识逻辑

TODO:这里到时候叙说

Student={
    --变量==>年龄,性别,姓名
    name="小明",--姓名
    age=18,--年龄
    sex=true,--性别==>这里通过true代表男
    --函数
    Eat=function (food)
        --如果采用这种方式进行书写,是与表中的name是毫无关系的,print中的name会看成全局变量哦
        --想要在表内部函数中调用表本身的属性和方法,一定要使用表名.属性或者表名.方法
        print(name)
        print("我们中午吃的:",food)
    end,
    Learn=function ()
        print("好好学习,天天向上")
    end,
    --在函数中调用自己属性或者方法的方式是把自己作为一个参数传进去,在内部访问
    Sleep=function (s)
        print(s.sex)
    end
}
Student.Sleep(Student)
--lua在申明表后,在表外是可以申明表没有的变量和方法
Student.hobby="踢足球,打篮球";
Student.Speak=function ()
    print(Student.name,"说话了")
end
function Student.SpeakTwo()
    print("又说话了")
end
--lua无法通过实例化对象new的方式,更像是一个类中有很多静态方法和变量,通过类名点的方式调用
print(Student.name,"今年",Student.age)
print(Student.Eat("汉堡包"))
Student.Speak();
print(Student.hobby);
Student.SpeakTwo();
Student:SpeakTwo();

--lua中的点和冒号的区别
--冒号调用方法会默认把调用者作为第一个参数传入方法中
function Student:SpeakThree()
    --lua中有一个关键字self表示默认传入的第一个参数
    print(self.name.."说话")
    
end
Student:SpeakThree()
Student.SpeakThree(Student)

三.运算符

算数运算符

(1)在lua中是没有自增自减和复合运算的 (2)字符串可以进行算数运算符操作,可自动转化成number

加法(+)减法(-)乘法(*)除法(/)取余(%) 幂(^) 整除(//)

条件运算符

大于(>) 小于(<) 大于等于(>=) 小于等于(<=) 等于等于(==) 不等于(~=)

逻辑运算符

  • 在lua中逻辑与==>and
  • 在lua中逻辑或==>or
  • 在lua中逻辑否==>not
  • 在lua中同样遵守逻辑运算的“短路”规则
print(true and false)
print(true or false)
print(not false)
*特殊知识记录
and 逻辑与 or 逻辑或
and or 他们不仅可以连接boolean ,任何东西都可以连接
在lua中只有nil和false才会认为是假
“短路”===>对于and 是有假则假,对于 or 则是有真则真
所以只需要判断第一个是否满足就会停止计算
对于运算符and来说,假设它的第一个操作数为假,就返回第一个操作数;不然返回第二个操作数
对于运算符or来说,假设它的第一个操作数为真。就返回第一个操作数,不然返回第二个操作数

位运算符

在lua中不支持位运算(& |),需要自己去实现

三目运算符

在lua中不支持三目运算符(?:),需要自身根据需要实现

模拟三目运算符

说明:这里x=3,y=2,所以很明显的说明x>y返回时true,然后继续看便是 true and x ,由于and是有假则假,所以true and x肯定是true了,对于or来说,true or y这个已经有一个真的了,所以直接返回3

x=3
y=2
--这里x>y返回的是true  (x>y) and x==>3 是真的,就不用看or了
local res=(x>y) and x or y
print(res)

说明:(1)这里x=1,y=3,所以x>y返回的是false,所以现在变化成了false and y,在前面逻辑运算符中说到过and的逻辑,当第一个值为假,则返回第一个值,所以r是false

(2)根据上一个所知转化为 false or y,根据or的逻辑,第一个为假,则会返回第二个值,所以res是3

x=1
y=3
--这里x>y返回的是false,则(x>y)==>false and x==>x ==>false or y or是真则真==>取y,则是3 
local r=(x>y) and x
print(r)
local res=(x>y) and x or y
print(res)

四.Lua循环和条件分支

注意:lua中是没有Switch语句的

1.条件分支语句

Lua 提供了以下条件分支语句:

语句 描述
[if 语句] if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
[if…else 语句] if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。
[if 嵌套语句] 你可以在ifelse if中使用一个或多个 ifelse if 语句 。

基本语法:

if 条件 then ……end

a=8
if a>5 then
    print("大于5")
end

基本语法

if 条件 then ……else ……end

a=22
if a>15 then
    print("大于15")
else
    print("小于15")
end

if 条件 then ……elseif 条件 then ……end

a=5
if a<5 then
    print("小于5")
elseif a==5 then
    print("等于5")
else
    print("大于5")
end 

2.循环语句

Lua 语言提供了以下几种循环处理方式:

循环类型 描述
[while 循环] 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。
[for 循环] 重复执行指定语句,重复次数可在 for 语句中控制。
[repeat…until] 重复执行循环,直到 指定的条件为真时为止
[循环嵌套] 可以在循环内嵌套一个或多个循环语句(while do … end;for … do … end;repeat … until;)

基本语法:

while 条件 do……end

num=0
while num<5 do
    print(num)
    num=num+1
end

基本语法

repeat ……until 条件(条件事结束条件)

num=0
repeat
    print(num)
    num=num+1
until  num>5

for 初始值,结束值,递增值

注意:lua中默认递增,i会默认+1,若保持默认递增值可以省略,若递减则需将递增值改为负数

print("****默认递增for循环****")
for i = 1, 10 do
    print(i)
end
print("****每次递增3的for循环****")
for i = 1, 10, 3 do
    print(i)
end
print("****每次递减1的for循环****")
for i = 5, 1, -1 do
    print(i)
end

五.多脚本执行

在实际的开发过程中,无论是Java还是C#都需要多脚本的调用

1.全局变量和本地变量

这里全局变量直接就可以定义。例如

a=123 --number类型
b="SeriousWission"  --字符串类型
c=true  --boolean类型

在c#中的本地变量(亦可以说局部变量)是可以用private【私有】protected(保护,只有子类可以访问),在lua中就比较简易了,直接用local 来表示,例如

local d="HaXinXi"  --局部字符串类型
local e=123456  --局部字符串类型

2.全局方法和本地方法

前面已经说明了全局变量和本地变量,其实全局方法和本地方法是和其相差不多的

-- 全局方法
funOne=function ()
    print("方法")
end
-- 本地方法
local funTwo=function ()
    print("local方法")
end

3.脚本的调用

前面两个标题已经说明了一大部分知识点了,下面就是脚本的调用

这里先写一个Test.lua文件作为后面使用

print("Test这里加载了这个lua文件测试")
testA="123456"
local testLocalA="456789"
funOne=function ()
    print("方法")
end
local funTwo=function ()
    print("local方法")
end
return testLocalA

在lua中如果想用其他脚本,需要使用一个关键字:require(‘脚本名’)/ require(“脚本名”)这两种格式都是可以的,例如:

require("Test")  -- 调用同路径中的Test脚本
funOne()  -- 这里因为上一行已经加载了Test脚本,而且Test脚本中funOne方法是共有的,所以可以直接使用
print(testA) --打印testA参数的值
print(testLocalA) -- 打印testLocalA参数的值

这里需要说明重要的点

相关说明
只要require执行完的脚本,任何全局变量,包括表,方法是可以直接拿来用的
这里需要注意若调用相同路径的文件可以直接使用文件名,而不同路径需要写入路径名
require不会重复加载同一个lua

4.脚本的卸载/移除

前面已经说完了脚本的声明调用,当这个脚本不需要了,该如何呢

先说明如何判断脚本是否声明过了

package.loaded["Test"]

当知道了判断脚本是否被调用了,在后面不需要了直接将其判断为空或者将判断是否调用过直接定义为false也是也是可以的

package.loaded["Test"]=nil  -- 将判断包是否加载过为nil
package.loaded["Test"]=false --将判断包是否加载过为false

5.局部变量使用

对于局部变量的使用可以在lua文件的最后一行返回一个局部参数,然后在需要的地方来用local 参数接收即可

local a=require("Test") --这里调用Test.lua文件,用local a来接收,其实收到的也就是Test.lua中的testLocalA
print(a)

6.大G表

_G是一个总表,本质是table,他将我们申明的所有全局变量都存储在其中

声明的local本地/局部变量是不会存到_G表中的

a=1
b="serious"
c=true
for key, value in pairs(_G) do
  print(key,value)
end
相关参考信息
string  table: 0000021B456413D0
ipairs  function: 00007FFDB36C9980        
setmetatable    function: 00007FFDB36C8B20
_G      table: 0000021B45633FA0
rawequal        function: 00007FFDB36C8C90
c       true
b       serious
load    function: 00007FFDB36C9EB0        
rawlen  function: 00007FFDB36C8DB0        
getmetatable    function: 00007FFDB36C8A40
assert  function: 00007FFDB36CA580        
debug   table: 0000021B45641690
package table: 0000021B45641050
os      table: 0000021B45641190
xpcall  function: 00007FFDB36CAA80
table   table: 0000021B45641510
dofile  function: 00007FFDB36CA3A0
io      table: 0000021B456411D0
pairs   function: 00007FFDB36C9780
next    function: 00007FFDB36C9640
loadfile        function: 00007FFDB36C9AC0
require function: 0000021B456412D0
utf8    table: 0000021B45640F90
tonumber        function: 00007FFDB36C85C0
select  function: 00007FFDB36CA620
a       1
rawget  function: 00007FFDB36C8E80
arg     table: 0000021B45641010
math    table: 0000021B456415D0
pcall   function: 00007FFDB36CA800
_VERSION        Lua 5.4
print   function: 00007FFDB36C8210
warn    function: 00007FFDB36C8330
collectgarbage  function: 00007FFDB36C91E0
type    function: 00007FFDB36C9590
coroutine       table: 0000021B45641490
tostring        function: 00007FFDB36CADA0
rawset  function: 00007FFDB36C8FF0

六.协同程序

前言:协程其实可以看作是一个线程对象,前面已经说了方法的定义与使用,这里就直接写一个方法

首先是一个比较简单的方法,打印的是SeriousWission这个字符串

fun=function ()
    print("SeriousWission")
end

协同程序的创建

这里需要说明的便是这两种方式的使用和运行是不一样的

第一种方式-coroutine.create

co=coroutine.create(fun)
print(co)  --打印结果 thread: 0000025793510878

第二种方式-coroutine.warp

co2=coroutine.wrap(fun)
print(co2)  -- function: 0000025793542920

这样看的还是不太明显,使用type()看看这两个协程程序的类型

-- 创建协程的第一种方式
co=coroutine.create(fun)
print(type(co))  --thread
-- 创建协程的第二种方式
co2=coroutine.wrap(fun)
print(type(co2))  --function

这样就能看出来这两种创建协程的方式类型是不同的,第一种方式本质是线程,但是第二种则本质是方法

协同程序的运行

这里运行协程程序因为前面用到的方式不同,使用也是不同的

--运行通过coroutine.create()创建的协程
coroutine.resume(co)
--运行通过coroutine.wrap()创建的协程
--直接用接收coroutine.wrap()的方法名运行即可
co2()--因为本质是方法,可以直接使用方法来运行

协同程序的挂起

这里需要注意一个点,c#的协程在启动后会按照一定时间不停的执行,但是lua只会执行一次

首先我们重新在定义一个方法并在里面放上协程的挂起函数

fun2 =function ()
    local i=0
    while true do
        i=i+1
        print("I Like Lua".."==>"..i)
        -- 协程挂起函数
        coroutine.yield()
    end
end

使用上面说到的运行协同程序的方式看看效果

--coroutine.create()创建
co3=coroutine.create(fun2)
--coroutine.resume()运行
coroutine.resume(co3)  --输出结果为:I Like Lua==>1
coroutine.resume(co3)  --输出结果为:I Like Lua==>2

--coroutine.wrap()创建
co4=coroutine.wrap(fun2)
--运行
co4() --输出结果为:I Like Lua==>1
co4() --输出结果为:I Like Lua==>2

即使while是一个死循环,这两种方式都只会执行一次,只有再创建相应的开始程序才会执行

协同程序的挂(加返回值)

协程的挂起说完了,其实协程是可以有返回值的

定义一个function()【为了方便直接使用fun2的】

fun3 =function ()
    local i=0
    while true do
        i=i+1
        print("I Like Lua".."==>"..i)
        -- 协程挂起函数
        coroutine.yield(i)
    end
end

执行这两个程序


-- 这里需要明白,是coroutine.resume这时有两个返回值,第一个返回的是程序是否执行成功,第二个是yield的值
co3=coroutine.create(fun3)
isOK,tempOne=coroutine.resume(co3)
print(isOK,tempOne)

-- coroutine.wrap返回的是没有程序是否成功这个值,只会返回程序中的值和yield的值
co4=coroutine.wrap(fun3)
print("返回值==>"..co4())

七.元表

首先先对元表是什么来一个大白话的解释

元表可以解释为一个表的父亲,也就是父表,
任何表变量都可以作为另一个表变量的元表
任何表变量都可以有自己的元表(父亲)
当我们子表中进行一些特定操作时,会执行元表的内容(其中包括tostring(),index()等)

  1. 设置元表

设置元表就相比较简单了==>setmetatable(子表,元表【可以看做是父表】),下面举个例子看看

mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表
  1. 特定操作

__tostring():当子表要被当做字符串使用时,会默认调用这个元表中的tostring方法

mytable2={
    __tostring=function()
        return "爱吃大饼的小虎"
    end
}   --定义一个表
mymetatable2={}  --定义一个元表
setmetatable(mytable2,mymetatable2)  --- 输出结果:爱吃大饼的小虎
print(myTable2)   --这里print是输出语句,里面输出的字符串,因此便会输出 爱吃大饼的小虎

同样里面function里面也是可以带参数的,那就再写一个

mytable3={
    __tostring=function(t)
        return t.name
    end
}   --定义一个表
mymetatable3={
	name="爱吃大饼的小虎"
}  --定义一个元表
setmetatable(mytable3,mymetatable3)  ---输出结果:爱吃大饼的小虎
print(mytable3)  --这里不难看出,子表需要知道父表有name这个参数,才可以输出

__call :在 Lua 调用一个值时调用:简单来说就是当做函数来用时调用

这里需要注意一点:只有元表里有__call元方法,才可以使用方法调用,不然会报错误

meta4={
    --当子表要被当做字符串使用时,会默认调用这个元表中的tostring方法
    --这里为了凸显出不同,特意换了一个字符串输出
    __tostring=function()
        return "爱吃大饼的小虎"
    end,
    __call=function ()
        print("七鳄学习格")
    end
}
myTable4={}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable4,meta4)
--当作函数来调用,这时不会调用__tostring(),而是__call()
myTable4()  --输出结果:七鳄学习格

这时 传入一个参数 会出现什么效果呢?

meta5={
    --当子表要被当做字符串使用时,会默认调用这个元表中的tostring方法
    --这里为了凸显出不同,特意换了一个字符串输出
    __tostring=function(t)
        return t.name
    end,
    __call=function (a)
        print(a)
        print("七鳄学习格")
    end
}
myTable5={
	name="黄雨涵123"
}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable5,meta5)
--当作函数来调用,这时不会调用__tostring(),而是__call()
myTable5(1)  
--输出结果:
		黄雨涵123
        七鳄学习格
--这里可能会意外,需要解释一下:当方法中带参数,会将myTable5本身传进去,所以打印的便是myTable5中的name参数的值,
--当__call=function (a)中在传入一个参数,是__call=function (a,b)时便会打印传入的数值1,
--简单来说第一个是表本身,第二个参数是传入的数值
  1. 元表中运算符

其实元表也是支持 运算符 的,下面就直接用程序做解释吧(以下罗列了常用的运算符)

翻阅Lua官网API可知:需要注意元表中的运算符只有小于或者小于等于,如果想实现大于或者大于等于可以选择取反实现
模式 描述
__add 对应的运算符 ‘+’.
__sub 对应的运算符 ‘-‘.
__mul 对应的运算符 ‘*’.
__div 对应的运算符 ‘/‘.
__mod 对应的运算符 ‘%’.
__unm 对应的运算符 ‘-‘.
__concat 对应的运算符 ‘..’.
__eq 对应的运算符 ‘==’.
__lt 对应的运算符 ‘<’.
__pow 对应的运算符‘^’
__le 对应的运算符 ‘<=’.

这里罗列了代码实现效果与输出结果,可能有些乱,但是看上去还是比较容易看明白的

meta5={
    ------对应的是运算符+,当执行加法执行此方法
    __add=function(t1,t2)
        return t1.age+t2.age
    end,
    ----对应的是运算符-,当执行减法执行此方法
    __sub=function (t1,t2)
        return t1.age-t2.age
    end,
    ----对应的是运算符*,当执行乘法执行此方法
    __mul=function (t1,t2)
        return t1.age*t2.age
    end,
    ----对应的是运算符/,当执行除法执行此方法
    __div=function (t1,t2)
        return t1.age/t2.age
    end,
    ----对应的是运算符==,当执行相等执行此方法
    __eq=function (t1,t2)
        return ""  --这里只会返回true或者false
    end,
    ----对应的是拼接,当执行拼接执行此方法
    __concat=function (t1,t2)
        return t1.str..t2.str
    end,
    ----对应的是运算符==,当执行相等执行此方法
    __lt=function (t1,t2)
        return ""  --这里只会返回true或者false
    end,
}
myTable5={age=1,str='黄雨涵'}
myTable6={age=2,str='真帅'}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable5,meta5)
setmetatable(myTable5,meta6)
print(myTable5+myTable6)  --3
print(myTable5-myTable6)  ---1
print(myTable5*myTable6)  --2
print(myTable5/myTable6)  --0.5
print(myTable5==myTable6) --false
print(myTable5..myTable6) --黄雨涵真帅
print(myTable5<myTable6)  --true

__index:当子表中,找不到某一个属性时,会到元表中的 _index中指定的表 去找索引;

__index遵循向上查找原则

这里其实有一个坑,举个例子阐述一下

meta7={
    age=1
}
myTable7={}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable7,meta7)
print(myTable7.age)	--输出结果:nil
解释:

这里很有可能会认为输出的是1,因为age=1,但是他的结果为nil,也就是意味着为空;在使用_index时需要用 _index指定表,如下代码:

这里有一个小小的坑:__index的赋值需要放到表外面赋值
meta7={
    age=1
}
--用元表指定所指定的表,这里就指向元表本身即可,其实指向一个新表也是可以的=>meta7.__index={age=2} 
meta7.__index=meta7
myTable7={}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable7,meta7)
print(myTable7.age) --输出结果:1

其实到这里就基本上够用了,但是再上升一个高度[层层查找]

meta7={
	--这里将myTable7的元表中的age注释上
    --age=1
}
meta7Father={
    age=1
}
meta7Father.__index=meta7Father
meta7.__index=meta7
myTable7={}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(meta7,meta7Father)
setmetatable(myTable7,meta7)
print(myTable7.age)  --输出结果:1
解释:

这里很显然,myTable7中没有age这个字段,然后到其元表的meta7中查找,但是meta7的age被注释上了,刚刚我们设置了meta7的元表是meta7Father,这个时候便会到meta7Father中查找,如果没有便无法在找了,便会返回nil,但是meta7Father中有age字段,便会返回1

__newindex:当赋值时,如果赋值一个不存在的索引,会把这个值赋值到newindex所指的表中,不会修改自己

__newindex同样遵循向上查找原则
meta8={}
meta8.__newindex={}
myTable8={}
--设置元表函数setmetatable(子表,元表【可以看做是父表】)
setmetatable(myTable8,meta8)
myTable8.age=10
--这时打印出的是nil
print(myTable8.age)
--如果想获取到age的值,则需要获取__newindex中的age
print(meta8.__newindex.age)  --输出结果:10
  1. 得到元表

getmetatable(table): 返回对象的元表(metatable

meta9={}
myTable9={}
setmetatable(myTable9,meta9)
print(getmetatable(myTable9))  
  1. 查找参数/设置参数

rawget():绕过__index,在自身身上查找有没有这个变量

meta10={}
myTable10={}
setmetatable(myTable10,meta10)
print(rawget(myTable10,"age"))

rawset():绕过__newindex,在自身身上设置变量

meta11={}
meta11.__newindex={}
myTable11={}
setmetatable(myTable11,meta11)
rawset(myTable11,"age",2)
print(myTable11)

八.面向对象

面向对象的三大特性:继承,封装,多态;

  • 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
  • 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

在lua中的对象便是由属性方法 组成,lua中最基本的结构 是table,所以需要用table来描述对象的属性


还差面向对象,垃圾回收,这篇文章就完事了