今天看Lua基础里面的迭代器和泛性for语义,感觉很有意思,特别是为什么闭包可以用作泛性for语义的迭代器这一点,需要思考一下,所以记录一下:
首先我们要知道循环过程中范性for在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。然后for语义执行的过程为:
第一,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数。
比如我们不用闭包的方式实现一个类似于ipair()的函数可以这样:
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function similar_ipairs_normal (a)
return iter, a, 0
end
如上所示,当我们使用for循环一个table时,可以这样用:
tb = {'one','two','three','four'}
for i,j in similar_ipairs_normal(tb) do
print(i,j)
end
我们按照范式for语义可以一步一步分析:
- 第一步:调用一次
similar_ipairs_normal (a)
函数,很显然,返回iter、a、0
分别对应:迭代函数 = iter、状态常量 = a、控制变量 = 0
。 - 第二步,将状态常量和控制变量作为参数让迭代函数调用,即
iter(a,0)
这样返回的结果就是1,v = a[1] = "one"
- 第三步,将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 1, j = "one"
- 第四步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
iter(a,1)
这样返回的结果就是2,v = a[2] = "two"
- 第五步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 2, j = "two"
- 第六步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
iter(a,2)
这样返回的结果就是3,v = a[3] = "three"
- 第七步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 3, j = "three"
- 第八步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
iter(a,3)
这样返回的结果就是4,v = a[4] = "four"
- 第九步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 4, j = "four"
- 第十步:继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
iter(a,4)
这时,可以看出i = 4+1 = 5,v = a[5] = nil
,这样就使得if v
不成立,就不会进入return i v
语句,这时显然返回的值为nil
,这样一来,循环就结束了。
如果使用闭包来代替范性for语义的迭代器的话,我们以这样使用:
function similar_ipairs_closure(a)
i = 0
return function()
i = i+1
local v = a[i]
if v then
return i,v
end
end
end
如上所示,当我们使用for循环一个table时,可以这样用:
tb = {'one','two','three','four'}
for i,j in similar_ipairs_closure(tb) do
print(i,j)
end
我们按照范式for语义可以一步一步分析:
- 第一步:调用一次
similar_closure (a)
函数,很显然,返回一个内部匿名函数function()
(这个function里面存着局部外部变量i = 0、待传入表a
),这时分别对应:迭代函数 = iter、状态常量 = nil、控制变量 = nil
。 - 第二步,将状态常量和控制变量作为参数让迭代函数调用,即
function(nil,nil) = function(),即直接调用匿名函数
,这样返回的结果就是1,v = a[1] = "one"
- 第三步,将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 1, j = "one"
- 第四步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
function(a,1) = function(),即直接调用匿名函数
这样返回的结果就是2,v = a[2] = "two"
- 第五步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 2, j = "two"
- 第六步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
function(a,2) = function(),即直接调用匿名函数
这样返回的结果就是3,v = a[3] = "three"
- 第七步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 3, j = "three"
- 第八步,继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
function(a,3) = function(),即直接调用匿名函数
这样返回的结果就是4,v = a[4] = "four"
- 第九步:将迭代函数返回的值赋值给参数列表,这样就使得参数
i = 4, j = "four"
- 第十步:继续循环,将状态常量和控制变量作为参数让迭代函数调用,即
function(a,4) = function(),即直接调用匿名函数
这时,可以看出i = 4+1 = 5,v = a[5] = nil
,这样就使得if v
不成立,就不会进入return i v
语句,这时显然返回的值为nil
,这样一来,循环就结束了。
根据上面的分析和对比可以知道,实际上闭包可以保持局部外部变量的特点使得范式for循环的状态变量和控制变量在自身内部被保持,这样也就简化了迭代函数的写法,闭包真的是非常好用呀。