一、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