写在前面:
写本系列**(自用)**的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。主要是代码部分。
二、Lua语法
13、多脚本执行
(1)全局变量和本地变量
像我们之前声明的a = 1,b = "123"等都是全局变量,甚至for循环中声明的变量也是全局变量,如下,变量c在for循环结束后依然存在。显然,这是很浪费的。
Lua
print("************多脚本执行************")
print("************全局变量和本地变量************")
a = 1
b = "123"
for i = 1, 2 do
c = "Max"
end
print(c)

如果想要声明本地(局部)变量,需要使用关键字 local,例如local a = 1,那么a就是一个局部变量,如下:
加上local关键字的d在循环中为Max,脱离循环后打印为nil;
fun函数中的局部变量tt,在外部打印显示为nil;
在外部定义的局部变量,可以打印。(不能打印的情况后面会说明)
Lua
for i = 1, 2 do
local d = "Max"
print("循环中的d " .. d)
end
print(d)
fun = function()
local tt = "123123123"
end
fun()
print(tt)
local tt2 = "555"
print(tt2)

(2)多脚本执行
我们另外创建一个脚本文件Test.lua,脚本内容如下。这里分别定义了全局变量testA和局部变量testLocalA
Lua
-- Test 脚本
print("Test测试")
testA = "123"
local testLocalA = "456"
返回原来的脚本。可以使用关键字 require("脚本名"),例如require("Test"),就可以在当前脚本中执行Test.lua中的代码。
require执行过的函数脚本,里面的全局变量都可以打印出来,但局部变量不可以,会为空。
Lua
print("************多脚本执行************")
require("Test")
print(testA)
print(testLocalA)

如果希望得到局部变量testLocalA,可以在Test.lua中将其返回出来,并且在执行require("Test")时接受返回值:testLocalA = require("Test")。这样就可以得到局部变量了。
Lua
-- Test 脚本
print("Test测试")
testA = "123"
local testLocalA = "456"
return testLocalA
(3)脚本卸载
如果是require加载执行的脚本,加载一次过后再次require,脚本不会再被执行。
可以使用package.loaded["脚本名"]来判断该脚本是否被执行,该方法返回值是bool。
使用package.loaded["Test"] = nil 即可完成脚本卸载
Lua
print("************脚本卸载************")
require("Test")
print(package.loaded["Test"])
package.loaded["Test"] = nil
require("Test")

从结果中可以看出,第一次require("Test")没有打印输出,此时检查脚本是否被执行返回值是true。卸载脚本后再次require("Test")得到了输出。
(4)大G表
_G表是一个总表(table) 他将我们声明的所有全局的变量都存储在其中,本地变量,即加了local的变量是不会存到大G表中的。
Lua
print("************大G表************")
for k,v in pairs(_G) do
print(k, v)
end
大G表部分内容如下:

14、特殊用法
(1)多变量赋值
多变量赋值很简单,lua语言可以同时为多个变量赋值
Lua
print("************特殊用法************")
print("************多变量赋值************")
a, b, c = 1, 2, "123"
print(a)
print(b)
print(c)

如果后面的值不够,自动补空
Lua
a, b, c = 1, 2
print(a)
print(b)
print(c)

如果后面的值多了,自动省略
Lua
a, b, c = 1, 2,3,4,5,6
print(a)
print(b)
print(c)

(2)多返回值
多返回值,之前提过,你用几个变量接就有几个值。如果你定义的变量少了,就少接几个。如果多了,就自动补空。
Lua
print("************多返回值************")
function Test()
return 10, 20, 30, 40
end
a, b, c = Test()
print(a)
print(b)
print(c)

(3)and or
and or 不仅可以连接Boolean,任何东西都能连接。虽然很少连接别的。
需要注意的是:lua中只有nil和false才认为是假
我们知道,lua也有"短路"---也就是对and来说,有假则假,对于or来说,有真则真。所以他们只需要判断,第一个条件是否满足,如果满足就会停止计算。
Lua
print("************and or************")
print(1 and 2) -- 1为真,需要判断第二个,因此打印2
print(0 and 1) -- 同上
print(nil and 1) -- nil为假,不用继续判断了,所以直接打印了nil
print(false and 2) -- 同上
print(true or 1) --和and理解方式一样
print(false or 2)

我们知道lua不支持三目运算符,但可以借助and和or的性质实现类似用法。
这里先判断(x > y) and x,(x > y)为真,and需要继续判断x是否为真,因此
(x > y) and x--->x
因此接下来判断x or b,由于x为3,为真,or不用继续往下判断,直接返回3
x or b--->x
res = x = 3
打印3
Lua
x = 3
y = 2
local res = (x>y) and x or b
print(res)

15、协同程序
(1)协程的创建
有两种方式,常用方式为coroutine.create()。可以顺手看看协程的类型,可以发现这种方式创建的协程的本质是一个线程对象:
Lua
print("************协同程序************")
print("************协程的创建************")
fun = function()
print(123)
end
co = coroutine.create(fun)
print(co)
print(type(co))

也可以写的高级点:
Lua
co = coroutine.create(function()
print(123)
end)
第二种方式是使用coroutine.wrap(),顺手看看协程的类型,可以发现这种方式创建的协程的本质是一个函数:
Lua
co2 = coroutine.wrap(fun)
print(co2)
print(type(co2))

(2)协程的运行
coroutine.create()创建的协程的运行方法是coroutine.resume()
Lua
print("************协程的运行************")
coroutine.resume(co)
coroutine.wrap()创建的协程的运行方法和函数调用相同
Lua
co2()

(3)协程的挂起
协程挂起函数,coroutine.yield(),执行这个函数就能让协程暂停
Lua
print("************协程的挂起************")
fun2 = function()
i = 1
while true do
print(i)
i = i + 1
coroutine.yield()
end
end
co3 = coroutine.create(fun2)
coroutine.resume(co3)
coroutine.resume(co3)
coroutine.resume(co3)

如上,使用了三次coroutine.resume(co3),分别打印出1,2,3
coroutine.yield()也能传入参数,传参就相当于返回值。需要注意的是,coroutine.create()创建的协程,默认第一个返回值是协程是否启动成功,第二个才是yield里面的返回值。
Lua
print("************协程的挂起************")
fun2 = function()
i = 1
while true do
print(i)
i = i + 1
coroutine.yield(i)
end
end
co3 = coroutine.create(fun2)
isOk, tempI = coroutine.resume(co3)
print(isOk, tempI)
isOk, tempI = coroutine.resume(co3)
print(isOk, tempI)
isOk, tempI = coroutine.resume(co3)
print(isOk, tempI)

coroutine.wrap()创建的协程调用,也可以有返回值,只是没有默认第一个返回值了
Lua
co4 = coroutine.wrap(fun2)
print("返回值" .. co4())
print("返回值" .. co4())
print("返回值" .. co4())

(4)协程的状态
可以通过coroutine.status(协程对象)获得协程状态,共有三种:
dead 结束
suspended 暂停
running 进行中
Lua
print("************协程的状态************")
print(coroutine.status(co3))
print(coroutine.status(co))
--可以得到当前正在运行的协程的线程号
print(coroutine.running())

当然我们可以回到刚刚执行的协程中,打印出协程的状态和协程是否在运行
Lua
fun2 = function()
i = 1
while true do
print(i)
i = i + 1
print(coroutine.status(co3))
print(coroutine.running())
coroutine.yield(i)
end
end
co3 = coroutine.create(fun2)
coroutine.resume(co3)
coroutine.resume(co3)
coroutine.resume(co3)
co4 = coroutine.wrap(fun2)
print("返回值" .. co4())
print("返回值" .. co4())
print("返回值" .. co4())

16、元表
(1)元表的概念及设置元表
任何表变量都可以作为另一个表变量的元表,任何表变量都可以有自己的元表(父亲)
当我们在子表中进行一些特定操作时,会执行元表中的内容。
设置元表的方式是:setmetatable(),第一个参数是子表;第二个参数是元表
Lua
print("************元表************")
print("************设置元表************")
meta = {}
myTable = {}
setmetatable(myTable, meta)
(2)特定操作__tostring
如果在元表中声明了__tostring方法,那么当子表要被当做字符串时,会默认调用这个元表中的__tostring方法。如果__tostring方法要使用参数,它有且只有这一个参数,就是__tostring的调用者,再继续声明参数如function(t, arg1, arg2),后面的参数也都是nil。
Lua
print("************元表的特定操作************")
print("************特定操作__tostring************")
meta2 = {
__tostring = function(t)
return t.name
end
}
myTable2 = {
name = "Susan"
}
setmetatable(myTable2, meta2)
print(myTable2)

(3)特定操作__call
当子表要被当做函数时 ,默认调用这个元表中的__call方法。__call方法第一个参数默认是调用者自己。
Lua
print("************特定操作_call************")
meta3 = {
-- 当子表要被当做字符串时 会默认调用这个元表中的__tostring方法
__tostring = function(t)
return t.name
end,
-- 当子表要被当做函数时 会默认调用这个元表中的__call方法
__call = function(a, b) --第一个参数默认是调用者自己
print(a)
print(b)
print("烤乳鸽好爱你")
end
}
myTable3 = {
name = "Susan"
}
setmetatable(myTable3, meta3)
myTable3(1)
上例使用myTable3(1),默认调用__call函数,传入两个参数(a = myTable, b = 1),代码运行到print(a)时将myTable作为字符串输出,会调用__tostring方法。因此输出如下:

(4)特定操作__运算符重载
主要记住运算符重载的函数名即可,例如__add是加法重载、__sub是减法重载。
Lua
print("************特定操作_运算符重载************")
meta4 = {
-- 运算符重载,子表使用+运算符时会调用该方法
-- 运算符 +
__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,
-- 运算符 %
__mod = function(t1, t2)
return t1.age % t2.age
end,
-- 运算符 ^
__pow = function(t1, t2)
return t1.age ^ t2.age
end,
-- 运算符 ==
__eq = function(t1, t2)
return t1.age == t2.age
end,
-- 运算符 <
__lt = function(t1, t2)
return t1.age < t2.age
end,
-- 运算符 <= --条件运算符只有这几个,没有> >= ~=
__le = function(t1, t2)
return t1.age <= t2.age
end,
-- 运算符 ..
__concat = function(t1, t2)
return t1.age .. t2.age
end
}
myTable4 = {age = 1}
setmetatable(myTable4, meta4)
myTable5 = {age = 2}
setmetatable(myTable5, meta4)
-- 表与表不能直接相加
print(myTable4 + myTable5)
print(myTable4 - myTable5)
print(myTable4 * myTable5)
print(myTable4 / myTable5)
print(myTable4 % myTable5)
print(myTable4 ^ myTable5)
--如果要用条件运算符来比较两个对象
--这两个元表的元表一定要一致
print(myTable4 == myTable5)
print(myTable4 < myTable5)
print(myTable4 <= myTable5)
--由于两个子表的元表一样,直接用 > 和 >= 也可以,反过来就是 < 和 <=
print(myTable4 > myTable5)
print(myTable4 >= myTable5)
print(myTable4 .. myTable5)

(5)特定操作__index和__newIndex
①__index
__index 说的是,当子表中找不到某一个属性时,会到元表中__index指定的表去找索引,__index 的查找可以一层一层往上找。
Lua
print("************特定操作__index和__newIndex************")
meta6Father = {
age = 1
}
meta6Father.__index = meta6Father
meta6 = {
--age = 1
--__index = {age = 2}
}
meta6.__index = meta6
--建议把__index写在表外面
--meta6.__index = {age = 2}
myTable6 = {}
setmetatable(meta6, meta6Father)
setmetatable(myTable6, meta6)
print(myTable6.age)
建议把__index写在表外面而非是表里面进行初始化。上例中设置了meta6Father的__index为meta6Father本身,meta6的__index也为meta6本身。若是myTable6中找不到某一个属性时,就会依次去meta6的__index,meta6Father的__index的中查找。

插播一条得到元表的方法是:
Lua
print(getmetatable(myTable6))

②rawget
rawget 当我们使用它时,会去找自己身上有没有这个变量。
Lua
print(rawget(myTable6, "age"))

③__newIndex
__newIndex 当赋值时,如果赋值一个不存在的索引,那么会把这个值赋值到__newIndex所指的表中,不会修改自己。
Lua
meta7 = {}
meta7.__newindex = {}
myTable7 = {}
setmetatable(myTable7, meta7)
myTable7.age = 1
print(myTable7.age)
print(meta7.__newindex.age)
rawset(myTable7, "age", 2)
print(myTable7.age)
可以看到,我们设置了myTable7.age = 1,但由于__newIndex的存在,接着打印myTable7.age仍然为空,而打印meta7.__newindex.age时不为空。
此时如果使用rawset 来设置myTable7.age,该方法会忽略__newIndex的设置,只会改自己的变量。此时可以打印出myTable7.age。
