在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
迭代器和闭包 迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。 迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。记住:闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。 举一个简单的例子,我们为一个list写一个简单的迭代器,与ipairs()不同的是我们实现的这个迭代器返回元素的值而不是索引下标: function list_iter (t) local i = 0 local n = table.getn(t) return function () i = i + 1 if i <= n then return t[i] end 这个例子中list_iter是一个工厂,每次调用他都会创建一个新的闭包(迭代器本身)。闭包保存内部局部变量(t,i,n),因此每次调用他返回list中的下一个元素值,当list中没有值时,返回nil.我们可以在while语句中使用这个迭代器: t = {10, 20, 30} iter = list_iter(t) -- creates the iterator while true do local element = iter() -- calls the iterator if element == nil then break end print(element) end 我们设计的这个迭代器也很容易用于范性for语句。 t = {10, 20, 30} for element in list_iter(t) do print(element) end 范性for为迭代循环处理所有的薄记(bookkeeping):首先调用迭代工厂;内部保留迭代函数,因此我们不需要iter变量;然后在每一个新的迭代处调用迭代器函数;当迭代器返回nil时循环结束。 范性for的语义 前面我们看到的迭代器有一个缺点:每次调用都需要创建一个闭包,大多数情况下这种做法都没什么问题,然而在有些情况下创建闭包的代价是不能忍受的。在这些情况下我们可以使用范性for本身来保存迭代的状态。 前面我们看到在循环过程中范性for在自己内部保存迭代函数,实际上它保存三个值:迭代函数,状态常量和控制变量.下面详细说明。 范性for的文法如下: for <var-list> in <exp-list> do <body> End <var-list>是一个或多个以逗号分割的变量名列表,<exp-list>是一个或多个以逗号分割的表达式列表,通常情况下exp-list只有一个值:迭代工厂的调用。 for k, v in pairs(t) do print(k, v) end 变量列表k,v;表达式列表pair(t)。 我们称变量列表中第一个变量为控制变量,其值为nil时循环结束。 下面我们看看范性for的执行过程: 首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。 第三,将迭代函数返回的值赋给变量列表。 第四,如果返回的第一个值为nil循环结束,否则执行循环体。 第五,回到第二步再次调用迭代函数。 更精确的来说: for var_1, ..., var_n in explist do block end 等价于: do local _f, _s, _var = explist while true do local var_1, ... , var_n = _f(_s, _var) _var = var_1 if _var == nil then break end block end end 如果我们的迭代函数是f,状态常量是s,控制变量的初始值是a0,那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、⋯⋯,直到ai=nil。 无状态的迭代器 每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。 a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end 迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs和迭代函数都很简单,我们在Lua中可以这样实现: function iter (a, i) i = i + 1 local v = a[i] if v then return i, v end end function ipairs (a) return iter, a, 0 end 当Lua 调用 ipairs(a)开始循环时,他获取三个值:迭代函数 iter,状态常量 a 和控制变量初始值0;然后Lua调用iter(a,0)返回1,a[1](除非a[1]=nil);第二次迭代调用iter(a,1)返回2,a[2]⋯⋯直到第一个非nil元素。 多状态的迭代器 很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。 下面我们写allwords迭代器,不适用闭包,而是使用带有两个域(line,pos)的table。 开始的迭代函数很简单,必须返回迭代函数和初始状态。 local iterator -- to be defined later function allwords()local state = {line = io.read(), pos = 1} return iterator, state end 真正的处理工作是在迭代函数内完成: function iterator (state) while state.line do --repeat while there are lines --search for next word local s,e = string.find(state.line,”%w+”,state.pos) if s then state.pos = e+1 return string.sub(state.line,s,e) else state.line = io.read() state.pos = 1 end end return nil end 我们应该尽可能的写无状态的迭代器,因为这样循环的时候由 for 来保存状态,如果不能用无状态的迭代器实现,应尽可能适用闭包;尽可能不要适用table这种方式。 |
请发表评论