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。lua

若是想用for语句写出一个无限循环,可与参考以下代码:翻译

for i = 1, math.huge do
    something
end

泛型for:

在了解泛型for以前咱们必须了解一下lua中的迭代器和闭包code

迭代器(iterator):

迭代器是一种可让咱们遍历一个集合中全部元素的代码结构。在lua种一般使用函数表示迭代器:每次调用函数的时候,函数会返回集合中的下一个元素。ip

一个典型的例子就是io.read,每次调用的时候它都会返回标准输入中的下一行,在没有能够读取的行的时候返回nil。原型

闭包:

根据迭代器的概念咱们应该能想到,迭代器在连续调用当中应该保存一些状态,这样才能知道当前迭代器的位置以及如何从当前位置进步到下一个位置。对于自定义迭代器而言,闭包为保存状态提供了良好的机制。工作流

闭包就是一个能够访问自身环境中的局部变量的函数。it

这些局部变量记录了迭代器连续调用过程当中的中间值,让闭包能记住迭代器的位置。固然要建立一个新的闭包,咱们还须要非局部变量。所以,一个闭包一般包含两个函数:

  1. 闭包自己
  2. 建立闭包和封装变量的“工厂”

如下是一个简单的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保存:

  1. 迭代函数
  2. 不可变状态
  3. 控制变量初始值

表达式产生的返回值最多保留三个,不足三个则以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)的返回值有两个:

  1. 随机次序返回k的下一个键,当key==nil的时候返回表中的第一个键,当全部表便利完毕,返回nil
  2. k对应的值,当k==nil的时候返回表中的第一个值

所以咱们能够不使用pairs而直接调用next,同样能够实现遍历效果

for i, v in next, {"hello", "lua", "for"} do
    print(i, v)
end

总结:

  1. lua中for循环有两种,数值类for泛型for。
  2. 迭代器也有两种,有状态的和无状态的。只有在for循环中才能实现无状态迭代器。
相关文章
相关标签/搜索