一、Lua是什么?
Lua 是一个小巧的脚本语言。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。这其中包括魔兽世界、博德之门、愤怒的小鸟、QQ三国、VOCALOID3、太阳神三国杀、游戏王ygocore等。
二、Lua环境安装
Window 系统上安装 Lua
window下你可以使用一个叫"SciTE"的IDE环境来执行lua程序,下载地址为:
https://github.com/rjpcomputing/luaforwindows/releases
双击安装后即可在该环境下编写 Lua 程序并运行。
其他系统上的安装请参考http://www.runoob.com/lua/lua-environment.html
三、Lua基础知识
1.print方法、单行和多行注释:
print('Hello World!')
-- 单行注释
--[[
多行注释
]]--
2.全局变量和局部变量:
a=10 -- 全局变量
local b=20 -- 局部变量
全局变量与局部变量与其他语言都类似,故不再说明。
3.赋空值
a=nil
Lua中,也可以对函数(方法)变量进行赋值nil,函数(方法)变量类似于函数(方法)指针
4.函数定义和调用(方法声明)
m=function()
print('Hello World')
end
m() -- 输出Hello World
5.关于函数返回值的问题
Lua函数允许返回多个返回值且返回值类型也可不一致
function fun(arg1,arg2,arg3)
return arg1..arg2,arg3 -- '..' 是 连接符,相当于C#的'+'
end
local t1,t2
t1,t2=fun('a','b','c') --可以以这种形式x,y,z....=Xxx(...)来获取返回值,它们是一一对应.
print(t1,t2) --输出无限制参数,Lua会在2个参数之间加上一个Tab分隔符 :结果 ab c
6.表
local t={'C#','C++','Java','JavaScript'}
print(#t) -- 使用#获取表t的长度
有关于使用'#'来获取长度的问题,可看
7.循环
local t={'C#','C++','Java','JavaScript'}
local i=1 -- 遍历t表内容,索引号起始位置为1,结束为止为4 (#t)
-- while condition do 处理 end
while i<=(#t) do
print(t[i])
i=i+1 -- Lua 不支持++ , +=
end
--2 相当于for(int j=1;j<(#t);j=j+1){}
for j=1,(#t),1 do
print(t[j])
end
-- 3 repeat 处理 until(condition) 肯定会执行一次
local k=1
repeat
print(t[k])
k=k+1
until(k>(#t))
8.多维数组
-- 制作一个二维数组
local t={ {'a','b'},{'c','d','e'}}
--遍历它
for i=1,(#t) do
local _t=t[i]
for j=1,(#_t) do
print(_t[j])
end
end
-- 动态创建一个多维数组
local m=0
local p={}
for i=1,3 do
p[i]={}
for j=1,i do
p[i][j]=m
m=m+1
end
end
-- 利用迭代器 pairs来遍历多维数组( {{1}, {2,3},{4,5,6} } ) 其实还是一个二维的,不过是矩阵
for k,v in pairs(p) do --k 是键 v 是 值 遍历 { {...},{...},{...} } 每个{}
for k1,v1 in pairs(v) do -- k1,v1同上 遍历{1},{2,3},{4,5,6}的内容
print(k1..','..v1)
end
print('')
end
1,0 --这里输出的内容是 相当于 p[1][1],那么键就是1,注意这个键是二维度的键
1,1 --同理 这里已经遍历到了 p[2] ,而输出的是p[2]表即{2,3}内容的键和值 p[2][1] 它的键是'1',值是p[2][1]
2,2 --相应地 p[2][2] 输出的第一个'2'是 p[2]表的'2'键,那么内容就是p[2][2]
1,3 --p[3][1] 即输出p[3]表的'1'键 和 对应内容
2,4 --p[3][2] 输出p[3]表的'2'键 和 对应内容
3,5 --p[3][3] 输出p[3]表的'3'键 和 对应内容
四、Lua较复杂的内容
4.1 Module模块概念
定义一个模块,一个模块一个.lua文件,下面的文件名叫module.lua
module = {} --模块其实就是表
module.var = "xb"
module.func1=function()
print("这个是module模块里面的func1函数")
end
--局部变量不可以加module.来标识
local function func2() --模块内的局部函数是不能在外部调用的 ,全局函数可以
print("这个是局部函数func2")
end
function module.func3() --另一种method定义形式
func2() --在全局函数里面用局部函数是可以的,外部也可以通过这样来调用func2()
print("这个是全局函数func3")
end
return module --模块module返回 一定要返回不然模块没有用
使用模块方法:
-- require "模块名"
-- require ("模块名") --模块名并不是表名而是文件名,例如将上面的文件名改为module1.lua,那么下面的也要改为require 'module1'
require "module" --相当于引用了整个module.lua文件,那个文件的内容会替换掉这句代码,下面开始使用module表
print(module.var)
module.func1()
module.func3()
xb
这个是module模块里面的func1函数
这个是局部函数func2
这个是全局函数func3
4.2 metatable元表概念
4.2.1元表的__index指定方法:
--1. funciton __index
--普通表
mytable={'C#','Java','C++'}
--元表
mymetatable={
__index=function(tab,key) --注意:__index 是有2个'_'在前面的,不要写成_index
print(tab[1]) --tab是元表对应的普表表
print(key) --key是在普通表mytable没有找到的键
return '1212'
end
}
--设置mytable的元表是mymetatable
mytable=setmetatable(mytable,mymetatable)
print(mytable[3]) -- 访问存在于普通表的键,正常输出C++,而不会去执行元表__index指定的方法
print(mytable['a']) -- mytable['a']返回了一个字符串'1212' 这个字符串的返回是因为执行了__index指定的方法
-- 总结: 当普通表mytable中没有对应的键值对时,会去调用mymetatable元表的__index指定的方法,输出tab(普通表)[1]元素和找不到的键,并返回'1212'字符串
C++
C#
a
1212
触发__index的方法是从普通表查不到键时,即这种情况也会触发q=mytable['a']
4.2.2__index指定一个表
--2.__index mytable mymetatable
mytable={'C#','Java','Lua'}
index_table={'XLua','Unity','Hotfix','Look'}
mymetatable={
__index=index_table --当普通表搜不到键值对时会去到__index指定的表搜 ,如果还是找不到,那就返回nil
}
mytable=setmetatable(mytable,mymetatable)
print(mytable[1])--输出 C#
print(mytable[4])--输出 Look (这个是在__index指定的表index_table拿到的)
C#
Look
4.2.3__newindex指定一个方法
-- __newindex Function
mytable={'c#','Java','phyton'}
mymetatable={
__newindex=function(tab,key,value)
print('表[1]'..tab[1])
print('新的键'..key)
print('值'..value)
--如果我要往元表插入数据
rawset(tab,key,value)
end
}
mytable=setmetatable(mytable,mymetatable)
mytable[1]='6'
mytable[100]='abc'
print('插入键值对='..mytable[100])
--当对普通表新增键值对时,会调用元表的__newindex指定的方法,tab是普通表,key是新增的键,value是新增的值
表[1]6
新的键100
值abc
abc
->在这里我突发奇想,测试一下mytable[100]='abc'会不会触发__index的方法或表
-- __newindex Function
mytable={'c#','Java','phyton'}
mymetatable={
__index=function(tab,key)
print('没找到键='..key)
return '__index返回给你'
end,
__newindex=function(tab,key,value)
print('表[1]'..tab[1])
print('新的键'..key)
print('值'..value)
--如果我要往元表插入数据
rawset(tab,key,value)
end
}
mytable=setmetatable(mytable,mymetatable)
mytable[1]='6'
mytable[100]='abc'
表[1]6
新的键100
值abc
可见,Lua并不是按照一般思路去思考的,对普通表进行赋值操作若新增键值对了,那么会触发__newindex指定的方法或表处理.
4.2.4__newindex指定一个表
--4 __newindex table
mytable={'C#'}
table={}
mymetatable={
__newindex=table
}
mytable=setmetatable(mytable,mymetatable)
mytable[1]='6'
mytable[100]='abc'
print(mytable[1]) --输出 6
print(mytable[100]) -- 输出 nil
print(table[1]) -- 输出 nil
print(table[100]) -- 输出 abc
--总结:当对普通表新增键值对的操作,不会作用于普通表,而是作用于元表__newindex指定的表
6
nil
nil
abc
4.2.5 __add , __call , __tostring
--5. __add --call --tostring
mytable={'C#','Java','C'}
mymetatable={
__add=function(tab1,tab2)
local max_index=0
for k,v in pairs(tab1) do
if k > max_index then
max_index=k
end
end
for k,v in pairs(tab2) do
max_index=max_index+1
table.insert(tab1,max_index,v)
end
return tab1
end,
__call=function(tab,arg,arg1) --默认的表函数,tab是元表对应的普通表,需要传递进2个参数
print(tab[1])
print(arg)
print(arg1)
return (arg..arg1)
end,
__tostring=function(tab) --tab是普通表
--利用pairs迭代器 遍历tab
for k,v in pairs(tab) do
print(k..','..v)
end
return '__tostring返回给你' --必须返回一个值
end
}
mytable=setmetatable(mytable,mymetatable)
mytable1={'KK','KE'}
print('__add函数调用方法')
newtable=mytable+mytable1
for k,v in pairs(newtable) do
print(k..','..v)
end
print('_call函数调用方法:')
local s = mytable(5,6)
print(s)
print('__tostring函数调用方法')
print(mytable) --必须这样使用,而且__tostring必须要有一个返回值
>lua -e "io.stdout:setvbuf 'no'" "05_metatable_add.lua"
__add函数调用方法
1,C#
2,Java
3,C
4,KK
5,KE
_call函数调用方法:
C#
5
6
56
__tostring函数调用方法
1,C#
2,Java
3,C
4,KK
5,KE
__tostring返回给你
>Exit code: 0
其中与操作模式上面列举了一个__add,还有如下:
__add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(注意:__是两个下划线)
模式 | 描述 |
__add | 对应的运算符 '+'. |
__sub | 对应的运算符 '-'. |
__mul | 对应的运算符 '*'. |
__div | 对应的运算符 '/'. |
__mod | 对应的运算符 '%'. |
__unm | 对应的运算符 '-'. |
__concat | 对应的运算符 '..'. |
__eq | 对应的运算符 '=='. |
__lt | 对应的运算符 '<'. |
__le | 对应的运算符 '<='. |
4.3 协同程序
-- 定义协同函数
co = coroutine.create( --直接里面加一个匿名函数来作为协同函数
function(a,b)
print(a+b)
print(coroutine.status(co)) --输出running状态
print(coroutine.running()) --返回线程号
coroutine.yield(a-b,a*100) --在yield中断方法里面写上2个返回值,返回给外部
print(a-b)
return a-b,a+b
end
)
print(coroutine.running()) --没有协同程序在运行 输出nil
print(coroutine.status(co)) --输出suspended挂机状态
--res2,res3获取yield内设置的返回值
res1,res2,res3=coroutine.resume(co,10,22)--开启协同程序输出a+b后会被coroutine.yield()暂停,但是不会影响主程序运行
print("i am here") --所以这里会继续输出
print(coroutine.running())--此时协同程序还是在暂停
print(coroutine.status(co)) --输出suspended挂机状态
print(res1,res2,res3)
res1,res2,res3=coroutine.resume(co) --继续运行协同程序 返回a-b,a+b res1是成功 运行标志位
print(res1,res2,res3) --第一个参数不是返回值 而是协同程序成功运行标志位,后面的才是返回值
print(coroutine.status(co)) --输出dead状态
coroutine.resume(co,10,20) -- 协程已死,无法启动
-- 可发现,协程的状态是只有在协同程序的方法里面才是运行状态,在外部都是处于挂起或者死亡状态的
>lua -e "io.stdout:setvbuf 'no'" "coroutine.lua"
nil
suspended
32
running
thread: 008FD4E8
i am here
nil
suspended
true -12 1000
-12
true -12 32
dead
>Exit code: 0
菜鸟教程的案例有误,在外部输出coroutine.running()必然是nil (亲测)
全面解析菜鸟教程的对于resume和yiled的灵活使用
function foo (a)
print("foo 函数输出", a) --a=1+1=2 很简单~~
return coroutine.yield(2 * a) --这里官方说的有点让人懵逼,应该是返回 2*a 的值 给外部!!!就是不是给local r的,而是给coroutine.resume(co,1,10)这个替换掉,相当于返回值
-- 1.外部调用print("main", coroutine.resume(co, 1, 10)) 后会一直执行卡在return 处,上面说了,coroutine.yield(2*a)返回了一个(2*a)给外部,即
-- print("main", coroutine.resume(co, 1, 10)) => 返回给外部的意思是: print("main", true, 4) true是啥?true是协同执行成功的意思,第三个参数就是上面yield返回的
-- 2.为了再次启动协程,即继续从 return 这里执行下去,外部调用 print("main", coroutine.resume(co, "r")) ,主要是调用了coroutine.resume(co,"r"),那么就继续了
-- 意思是继续执行co,并且传递一个"r",注意,这里传递的r 与第一次调用resume(co,1,10)的传递不同,它是传递给 coroutine.yield(2*a)的,也就是
-- return coroutine.yield(2 * a) => return 'r' ,主要理解了这一点,我们下面的就很好理解了,弄清楚yield到底给谁传递值,resume给谁传递值就没啥问题了.
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10 这个肯定都理解 a=1,b=10
local r = foo(a + 1) -- 到这里也能理解去到了foo()函数 ,然后经过上面的解释,你应该清楚r='r'
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入 这里也不难理解,yield给外部传递了3个参数(true,a+b,a-b),true是自动传递的
-- 再解释一次吧,这里yiled返回的值是给coroutine.resume(co, "r"),那么print("main", coroutine.resume(co, "r"))=>print("main",true,a+b.a-b)
--再次陷入等待启动,在foo中,是由coroutine.resume(co, "r")启动,而这里由 coroutine.resume(co, "x", "y")启动,resume传递了2个参数,即local r,s="x","y"
print("第三次协同程序执行输出", r, s) --轻松理解^.^
return b, "结束协同程序" -- 这里的b值就是第一次调用resume传递的b值,也就是10啦,官网说错了-_-!!!,这里直接用return结束协同
-- 再解释下,相当于给print("main", coroutine.resume(co, "x", "y"))的coroutine.resume(co,"x","y")替换成 true,10,"结束协同程序"
end)
print("main", coroutine.resume(co, 1, 10)) -- coroutine.resume(co,1,10)会启动协程co,进入foo()方法后卡在coroutine.yield(2*a)处,此时return 方法还没完全执行
-- 注意:下面开始解释协同注意看了
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9 --顺着我的思路走的话,不难理解这里会输出 true 11 -9.
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
--此时协同已经over
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
>lua -e "io.stdout:setvbuf 'no'" "coroutine.lua"
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
>Exit code: 0