Generic for中使用pairs和ipairs的一个陷阱

    Lua有一种很自然的循环方式,即Generic for。它的格式是这样的:

for namelist in iterator do
   block
end

其中iterator是一个迭代器函数,它可以有一个或多个返回值,namelist是逗号分隔的循环变量名列表,用来接收每次调用迭代器函数得到的返回值。这段程序的语义与下面形式的代码相同:

while true do
   local namelist = iterator()
   if nil == first(namelist) then break end
   block
end

first(namelist)表示namelist的第一个变量名。

    Lua已经提供了若干个常用的迭代器生成函数,最常见的就是ipairs和pairs。ipairs(t)以一个table作为参数,生成一个依次返回(1,t[1])、(2,t[2])、(3,t[3])......的迭代器。下面这段代码

t = {"Monday","Tuesday", "Wednesday","Thursday", "Friday","Saturday","Sunday"}

for i,v in ipairs(t) do
   print(i,v)
end

将会打印出

1        Monday
2        Tuesday
3        Wednesday
4        Thursday
5        Friday
6        Saturday
7        Sunday

。pairs同样用一个table作为参数,它生成的迭代器依次返回该表中所有的key和value。

    Generic for搭配ipairs和pairs用起来相当方便,但是它们却有一个很难察觉的缺陷。看看这段代码:

for i,v in ipairs(t) do
  print(i,v)
  -- some code changes i
  i = 0
end

许多人(包括我)都猜不出它竟然会不停地疯狂打印

1        Monday

,如果用pairs替代ipairs也不会好到哪里去: 在打印出一行数据后便抛出个 invalid key for `next' 错误。

    由此可知,使用了ipairs和pairs的Generic for在语义与上文while true ... end形式的代码并不完全相同,它们生成的迭代器需要使用循环变量,而如果循环体改变了循环变量值的话,那么迭代器就很可能会象在本例中那样找不着北了。因此,《Programming in Lua》的4.3.5中便有这么一段谆谆告诫: “The generic loop shares two properties with the numeric loop: The loop variables are local to the loop body and you should never assign any value to the loop variables. "。

    ipairs和pairs的这个缺陷是由于它们要用当前循环变量的值来作为下一次迭代的起点,那么这是不是必需的呢?当然不是,我们完全可以通过closure来实现一个更安全的ipairs :

function myipairs (t)
   local i = 0
   local n = table.getn(t)
   return function ()
             i = i + 1
             if i <= n then return i,t[i] end
          end
end

t = {"Monday","Tuesday", "Wednesday","Thursday", "Friday","Saturday","Sunday"}

for i,v in myipairs(t) do
  print(i,v)
  -- some code changes i
  i = 0
end

    试试上面这段代码,怎么样?是不是工作得很好?为什么它不会发疯?其实很简单,myipairs生成的迭代器(一个closure)把需要用到的迭代信息保存到它的upvalue(i和n)中,这样即使循环体更改了循环变量也不会影响到迭代状态。感谢closure,我们终于可以放心大胆地粗心一点了!^_^

你可能感兴趣的:(function,properties,table,iterator,lua,variables)