五.函数
在lua中,若一个函数只有一个参数,并且此参数是一个字符串或table构造式,那么圆括号便可不写。
print "hello world" --等同于print("hello world")
print {10,20} --等同于print({10,20})
多重返回值:lua允许函数返回多个结果。根据情况,函数返回值的个数也不同
a.若将函数作为表达式的一部分来调用时,只保留函数的第一个返回值
b.当一个函数调用是一系列表达式中的最后一个元素(或仅有一个元素)时,才能获得函数的所有返回值
这里的"一系列表达式"在lua中表现为4种情况:多重赋值、函数调用时传入的实参列表、table的构造式和return语句
function foo0() end
function foo1() return "a" end
function foo2() return "a","b" end
---------------------------a----------------------
x,y = foo2(),20
print(x,y) --a 20
print(foo2().."x") --ax
t = {foo2(),4} --t[1]="a",t[2]=4
---------------------------b----------------------
--1.多重赋值
x,y,z,w = 1,foo2()
print(x,y,z,w) --1 a b nil
--2.函数调用时传入的实参列表
print(foo2()) --a b
--3.table的构造式
t = {foo2()} --t = {"a","b"}
--4.return语句
function foo()
return foo2()
end
print(foo()) --a b
--可以将函数放入一对圆括号中,从而迫使它只返回一个结果
print((foo2())) --a
--函数unpack,接受一个数组作为参数,并从下标1开始
--返回该数组的所有元素
print(unpack({10,20,30})) --10 20 30
变长参数:
--参数表中的...表示该函数可接受不同数量的实参
function add(...)
local sum = 0
for i,v in ipairs({...}) do
sum = sum + v
end
return sum
end
print(add(1,2,3,4)) --10
前面提到,函数是可以存储在变量中的。函数与其他所有值都是匿名的,即它们都没有名称。当讨论一个函数名时,实际上是在讨论一个持有某函数的变量。
a = {p = print}
a.p("hello woeld") --hello woeld
print = math.sin
a.p(print(3.14159)) --约等于0
sin = a.p
sin(10,20) --10 20
所以对于:
function foo(x) return 2*x end
其实就是等于:
foo = function (x) return 2*x end
--匿名函数的使用
--table库提供了一个函数table.sort,它接受一个table并对其排序
--它的第二个参数是一个匿名函数,该函数接受两个元素,并返回
--在有序情况下第一个元素是否应该排在第二个元素之前
a = {
{name = "aa",hp = "50"},
{name = "zz",hp = "30"},
{name = "cc",hp = "70"},
}
table.sort(a,function(a,b) return (a.name > b.name) end)
for i,v in ipairs(a) do
print(v.name) --zz cc aa
end
若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为"语法域"。
假如有这样一个例子:
--根据年级对姓名进行排序
names = {"peter","paul","marry"}
grades = {marry = 10,paul = 7,peter = 8}
table.sort(names,function (n1,n2)
return grades[n1] > grades[n2]
end)
for i,v in ipairs(names) do
print(v) --marry peter paul
end
现在要用一个函数来完成上面的排序,即对排序函数进行封装:
--根据年级对姓名进行排序
names = {"peter","paul","marry"}
grades = {marry = 10,paul = 7,peter = 8}
--[[table.sort(names,function (n1,n2)
return grades[n1] > grades[n2]
end)--]]
function sortByGrade(names,grades)
table.sort(names,function (n1,n2)
return grades[n1] > grades[n2]
end)
end
sortByGrade(names,grades)
for i,v in ipairs(names) do
print(v) --marry peter paul
end
可以发现sort的匿名参数可以访问grades,而grades是外部函数sortByGrade的局部变量。在这个匿名函数的内部,grades既不是全局变量也不是局部变量,将其称为一个"非局部的变量"。
再来看看这个:
function count()
local i = 0
return function()
i = i + 1
return i
end
end
c = count()
print(c()) --1
print(c()) --2
在这段代码中,匿名函数访问了一个"非局部的变量"i,该变量用于保持一个计数器。初看上去,由于创建变量i的函数(count)已经返回,所以之后每次调用匿名函数时,i都应该是超出了作用范围的,但其实不然,lua会以closure(闭合函数)的概念来正确地处理这种情况。简单地讲,一个closure(闭合函数)就是一个函数加上该函数所需访问的所有"非局部的变量"。就以上例而言,closure(闭合函数)就是匿名函数加上变量i。对上例进一步修改:
function count()
local i = 0
return function()
i = i + 1
return i
end
end
c = count()
print(c()) --1
print(c()) --2
c2 = count()
print(c2()) --1
print(c()) --3
print(c2()) --2
因此c和c2是同一个函数所创建的两个不同的closure,它们各自拥有局部变量i的独立实例。
从技术上来讲,lua中只有closure,而不存在"函数",因为函数本身就是一种特殊的closure。不过只要不会引起混淆,仍将采用"函数"来指代closure。
非全局的函数:
由于函数是一种"第一类值",因此一个显而易见的推论就是,函数不仅可以存储在全局变量中,还可以存储在table的字段中和局部变量中。
a = {}
a.foo = function (x,y)
return x + y
end
a.goo = function (x,y)
return x - y
end
print(a.foo(1,2)) --3
print(a.goo(4,3)) --1
---------------------------------------------------
b = {
foo = function(x,y) return x + y end,
goo = function(x,y) return x - y end,
}
print(b.foo(1,2)) --3
print(b.goo(4,3)) --1
---------------------------------------------------
c = {}
function c.foo(x,y) return x + y end
function c.goo(x,y) return x - y end
print(c.foo(1,2)) --3
print(c.goo(4,3)) --1
只要将一个函数存储到一个局部变量中,即得到了一个"局部函数",也就是说该函数只能在某个特定的作用域中使用。
六.协同程序
协同程序跟线程差不多,区别在于:
一个具有多个线程的程序可以同时运行几个线程;
一个具有多个协程的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起(suspend)时,它的执行才会暂停。
一个协同程序可以处于4种不同的状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)。当创建一个协同程序时,它处于挂起状态,不会自动执行其内容。
lua将所有关于协同程序的函数放置在一个名为"coroutine"的table中:
函数create用于创建新的协同程序,它只有一个参数,就是一个函数。该函数的代码就是协同程序所需执行的内容。create会返回一个thread类型的值,用以表示新的协同程序。通常create的参数是一个匿名函数。
函数status可以用来检查协同程序的状态。
函数resume用于启动或再次启动一个协同程序,并将其状态由挂起改为运行。
co = coroutine.create(function ()
print("hi")
end)
print(co) --thread: 002FC6B8
print(coroutine.status(co)) --suspended
coroutine.resume(co) --hi
print(coroutine.status(co)) --dead
协同程序的强大之处在于函数yield的使用,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行。
co = coroutine.create(function ()
for i = 1,10 do --默认递增1
print("hi",i)
coroutine.yield()
end
end)
print(coroutine.status(co)) --suspended
coroutine.resume(co) --hi 1
print(coroutine.status(co)) --suspended
coroutine.resume(co) --hi 2
print(coroutine.status(co)) --suspended
当一个协同程序A唤醒另一个协同程序B时,协同程序A就处于一种特殊的状态,既不是挂起状态(A无法执行),也不是运行状态(是B在运行)。所以将此时的状态称为正常状态。
yield也是有返回值的:
co = coroutine.create(function (a,b,c)
print("hi",a,b,c)
end)
coroutine.resume(co,1,2,3) --hi 1 2 3
print(coroutine.resume(co,1,2,3)) --false cannot resume dead coroutine
-------------------------------------------------------
co = coroutine.create(function (a,b)
coroutine.yield(a + b,a - b)
end)
print(coroutine.resume(co,20,10)) --true 30 10
--true表示没有错误
-------------------------------------------------------
co = coroutine.create(function ()
print("hi",coroutine.yield())
end)
print(coroutine.status(co)) --suspended
coroutine.resume(co,2,3) --无输出,因为被挂起了
print(coroutine.status(co)) --suspended
coroutine.resume(co,4,5) --hi 4 5,yield的返回值是对应的resume的传入值
print(coroutine.status(co)) --dead
-------------------------------------------------------
co = coroutine.create(function ()
return 6,7
end)
print(coroutine.resume(co)) --true 6 7