lua for循环
在lua种,for语句有两种形式
- 数值型(numerical)
- 泛型(generic)
数值型for:
基本语法如下
for var exp1, exp2, exp3 do
something
end
在循环开始之前,每个表达式(exp1,exp2,exp3)都会执行一遍,循环开始后就不再执行。
function exp1() print("exp1") return 1 end
function exp2() print("exp2") return 5 end
function exp3() print("exp3") return 1 end
for i = exp1(), exp2(), exp3() do
print(i)
end
输出:
exp1
exp2
exp3
1
2
3
4
5
var是被for自动声明的局部变量,初始值为exp1,作用范围仅限于for循环内部。var的值小于等于exp2之前的每次循环中都会执行something,每次循环结束后都会将步长(exp3)增加到var上,exp3是可选的,如果没有指定,默认步长为1。如果想跳出循环可以使用break。
如果想用for语句写出一个无限循环,可与参考如下代码:
for i = 1, math.huge do
something
end
泛型for:
在了解泛型for之前我们必须了解一下lua中的迭代器和闭包
迭代器(iterator):
迭代器是一种可以让我们遍历一个集合中所有元素的代码结构。在lua种通常使用函数表示迭代器:每次调用函数的时候,函数会返回集合中的下一个元素。
一个典型的例子就是io.read,每次调用的时候它都会返回标准输入中的下一行,在没有可以读取的行的时候返回nil。
闭包:
根据迭代器的概念我们应该能想到,迭代器在连续调用当中应该保存一些状态,这样才能知道当前迭代器的位置以及如何从当前位置进步到下一个位置。对于自定义迭代器而言,闭包为保存状态提供了良好的机制。
闭包就是一个可以访问自身环境中的局部变量的函数。
这些局部变量记录了迭代器连续调用过程中的中间值,让闭包能记住迭代器的位置。当然要创建一个新的闭包,我们还需要非局部变量。因此,一个闭包通常包含两个函数:
- 闭包本身
- 创建闭包和封装变量的“工厂”
以下是一个简单的table迭代器,它遍历table返回所有的值
function values(t) --创建闭包和封装变量的“工厂”
local i = 0
print("create iter")
return function() --闭包本身
i = i + 1
print("call iter", i)
return t[i]
end
end
在这个例子中,values作为工厂,每当它被调用的时候,就会创建一个新的闭包(迭代器本身)。这个闭包将自身的状态保存在外部变量"t"和"i"中,这两个变量是由values封装的。每次调用迭代器,i的值加1,然后返回t[i],i作为迭代器的外部变量,在迭代器返回后不会被清除,其值得以保存下来,起到了记录迭代器位置的作用。在遍历完table的最后一个元素后,迭代器返回nil,表示迭代结束。
我们可以在一个while循环中使用这个迭代器,它展示了迭代器的完整工作流程:
iter = values({1, 2, 3, 4})
while true do
local v = iter()
if not v then
break
end
print(v)
end
输出:
create iter
call iter 1
1
call iter 2
2
call iter 3
3
call iter 4
4
call iter 5
使用泛型for,我们可以以更简洁的代码实现上述逻辑。
for f in values({1, 2, 3, 4}) do
print(f)
end
为什么泛型for能实现如此简洁的代码?下面我们就来探究一下泛型for背后做了哪些事情。
泛型for的标准语法如下:
for var-list in exp-list do
body
end
- var-list:一个或多个变量组成的列表,由逗号分隔
- exp-list:一个或多个表达式组成的列表,同样由逗号分隔
我们把变量列表(var-list)中的第一个变量称为控制变量(control variable),其值永远不会是nil,因为当其值为nil时循环已经结束了。
泛型for要做的第一件事就是对 exp-list 求值,这些表达式应该返回三个值供泛型for保存:
- 迭代函数
- 不可变状态
- 控制变量初始值
表达式产生的返回值最多保留三个,不足三个则以nil补齐。例如上面的values()工厂只返回了迭代函数,所以不可变状态和控制变量初始值均为nil。用代码表示上述过程就是:
-- _f 迭代函数
-- _s 不可变状态
-- _v 控制变量初始值
local _f, _s, _v = exp-list
然后泛型for把_s和_v作为参数调用_f,如果返回的第一个值(控制变量)为nil,表示循环结束,否则,把_s和新的返回值作为参数,继续调用_f。其调用过程类似于数列,用伪代码可以表示为:
local _v1, ... = _f(_s, _v)
local _v2, ... = _f(_s, _v1)
local _v3, ... = _f(_s, _v2)
...
local _vn, ... = _f(_s, _vn-1)
每次循环产生一次调用。这里_f可以返回多个值,但是只有第一个返回值才是控制变量,用来决定循环是否还要继续。
所以一个形如:
for var_1, var_2, ... in explist do something end
的泛型for语句可以翻译为:
local _f, _s, _v = exp-list
while true do
local var_1, var_2, ... = _f(_s, _v)
_v = var_1
if not _v then break end
something
end
与上面最原始的迭代器相比较,这里的迭代函数_f有一个显著的区别,就是_f可以接收参数了,并且参数的值就包含了当前迭代器的状态,也就是说迭代器自身不需要保存状态了。因此诞生了一个新的概念:
无状态迭代器
顾名思义,无状态迭代器就是一种不需要保存任何状态的迭代器。因此在多个循环中可以使用同一个迭代器,从而避免了创建闭包的开销,让代码在性能上得到了提升。ipairs就是一个典型的无状态迭代器。例如:
for i, v in ipairs({"hello", "lua", "for"}) do
print(i, v)
end
我们自己可以用lua实现一个ipairs迭代器
local function my_iter(t, i)
print("call:"..i)
i = i + 1
local v = t[i]
if v then
return i, v
end
end
function my_ipairs(t)
print("init")
return my_iter, t, 0
end
for i, v in my_ipairs({"hello", "lua", "for"}) do
print(i, v)
end
输出:
init
call:0
1 hello
call:1
2 lua
call:2
3 for
call:3
在上述示例中,调用for循环的第一步就是对my_ipairs求值,_f, _s, _v分别获得返回值:
_f = my_iter
_s = t
_v = 0
在第一次循环中执行:
i, v = _f(t, _v) --等价于 i, v = my_iter(t, 0)
执行结果为:
i = 1
v = t[1]
紧接着第二次循环,泛型for会把第一次循环中_f(t, _v)的第一个返回值(i)作为控制变量,进行第二次调用:
i, v = _f(t, _v) --等价于 i, v = my_iter(t, 1)
执行结果为:
i = 2
v = t[2]
按照此规律进行迭代,直至i的值为nil。
pairs函数也是一个无状态迭代器,它调用的是lua中一个基本的迭代函数:next(t, k)。pairs的原型可以描述如下:
function pairs(t)
return next, t, nil
end
next(t, k)的返回值有两个:
- 随机次序返回k的下一个键,当key==nil的时候返回表中的第一个键,当所有表便利完毕,返回nil
- k对应的值,当k==nil的时候返回表中的第一个值
因此我们可以不使用pairs而直接调用next,一样可以实现遍历效果
for i, v in next, {"hello", "lua", "for"} do
print(i, v)
end
总结:
- lua中for循环有两种,数值类for泛型for。
- 迭代器也有两种,有状态的和无状态的。只有在for循环中才能实现无状态迭代器。