]]>Iterators and the Generic for(LUA)http://www.shnenglu.com/swo2006/archive/2006/11/28/15725.htmlswoswoTue, 28 Nov 2006 04:59:00 GMThttp://www.shnenglu.com/swo2006/archive/2006/11/28/15725.htmlhttp://www.shnenglu.com/swo2006/comments/15725.htmlhttp://www.shnenglu.com/swo2006/archive/2006/11/28/15725.html#Feedback0http://www.shnenglu.com/swo2006/comments/commentRss/15725.htmlhttp://www.shnenglu.com/swo2006/services/trackbacks/15725.html在这一章我们讨Zؓ范性for写P代器, 我们从一个简单的q代器开?然后我们学习如何通过利用范性for的强大之处写出更高效的P代器. 7.1 q代器与闭包 q代器是一U支持指针类型的l构,它可以遍历集合的每一个元?在Lua中我们常怋用函数来描述q代?每次调用该函数就q回集合的下一个元?
q代器需要保留上一ơ成功调用的状态和下一ơ成功调用的状?也就是他知道来自于哪里和要前往哪里.闭包提供的机制可以很Ҏ实现q个d.C:闭包
是一个内部函?它可以访问一个或者多个外部函数的外部局部变?每次闭包的成功调用后q些外部局部变量都保存他们的?状?.当然如果要创Z个闭?
必须要创建其外部局部变?所以一个典型的闭包的结构包含两个函?一个是闭包自己;另一个是工厂(创徏闭包的函?. 举一个简单的例子,我们Z个list写一个简单的q代?与ipairs()不同的是我们实现的这个P代器q回元素的D不是烦引下? 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 end end q个例子中list_iter 是一个工?每次调用他都会创Z个新的闭?q代器本w?.闭包包村内部局部变?t,i,n),因此每次调用他返回list中的下一个元素?当list中没有值时,q回nil.我们可以在while语句中用这个P代器: 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 我们设计的这个P代器也很Ҏ用于范性for语句 t = {10, 20, 30} for element in list_iter(t) do print(element) end 范性forP代@环处理所有的薄记(bookkeeping):首先调用q代工厂;内部保留q代函数,因此我们不需要iter变量;然后在每一个新的P代处调用q代器函?当P代器q回nil时@环结?后面我们看到范性for能胜L多的d). 下面看一个稍微高U一点的例子:我们写一个P代器遍历一个文件内的所有匹配的单词.Z实现目的,我们需要保留两个?当前行和在当前行的偏U量,我们使用两个外部局部变量line,pos保存q两个? function allwords () local line = io.read() -- current line local pos = 1 -- current position in the line return function () -- iterator function while line do -- repeat while there are lines local s, e = string.find(line, "%w+", pos) if s then -- found a word? pos = e + 1 -- next position is after this word return string.sub(line, s, e) -- return the word else line = io.read() -- word not found; try next line pos = 1 -- restart from first position end end return nil -- no more lines: end of traversal end end
q代函数的主体部分调用了string.find函数,string.find在当前行从当前位|开始查扑配的单词,例子中匹配的单词使用模式'%w+
'描述?如果查找C个单?q代函数更新当前位置pos为单词后的第一个位|?q且q回q个单词(string.sub函数从line中提取两个位|?
参数之间的子?.否则q代函数d新的一行ƈ重新搜烦.如果没有line可读q回nill束. 管q代函数有些复杂,但用v来是很直观的: for word in allwords() do print(word) end 通常情况?q代函数都难写易?q不是一个大问题:一般Lua~程不需要自己定义P代函?而是使用语言提供?除非实需要自己定? 7.2 范性for的语义?
前面我们看到的P代器有一个缺?每次调用都需要创Z个闭?大多数情况下q种做法都没什么问?例如在allwordsq代器中创徏一个闭包的代h?
赯整个文g来说微不道,然后在有些情况下创徏闭包的代h不能忍受?在这些情况下我们可以使用范性for本n来保存P代的状? 前面我们看到在@环过E中范性for在自己内部保存P代函?实际上它保存三个?q代函数,状态常量和控制变量.下面详细说明. 范性for的文法如? for <var-list> in <exp-list> do <body> end <var-list>是一个或多个以逗号分割变量名的列表,<exp-list>是一个或多个以逗号分割的表辑ּ列表,通常情况下exp-list只有一个?q代工厂的调? for k, v in pairs(t) do print(k, v) end 变量列表k,v;表达式列表pair(t),在很多情况下变量列表也只有一个变?比如: for line in io.lines() do io.write(line, '\n') end 我们U变量列表中W一个变量ؓ控制变量,其gؓnil时@环结? 下面我们看看范性for的执行过E? 首先,初始?计算in后面表达式的?表达式应该返回范性for需要的三个?q代函数,状态常量和控制变量;与多Dg?如果表达式返回的l果个数不三个会自动用nil补Q多出部分会被忽? W二,状态常量和控制变量作ؓ参数调用q代函数(注意:对于forl构来说,状态常量没有用?仅仅在初始化时获取他的值ƈ传递给q代函数). W三,P代函数返回的Dl变量列? W四,如果q回的第一个gؓnil循环l束,否则执行循环? W五,回到W二步再ơ调用P代函? 更精的来说: for var_1, ..., var_n in explist do block end {h?br /> 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 如果我们的P代函数是fQ状态常量是s,控制变量的初始值是a0,那么控制变量@?a1=f(s,a0);a2=f(s,a1);...直到ai=nil 7.3 无状态的q代?br /> 无状态的q代器是指不保留M状态的q代?因此在@环中我们可以利用无状态P代器避免创徏闭包p额外的代? 每一ơP?q代函数都是用两个变?状态常量和控制变量)的g为参数被调用,一个无状态的q代器只利用q两个值可以获取下一个元?q种无状态P代器的典型的单的例子是ipairs,他遍历数l的每一个元? a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end q代的状态包括被遍历的表(循环q程中不会改变的状态常?和当前的索引下标(控制变量),ipairs和P代函数都很简?我们在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)开始@环时,他获取三个?q代函数iter,状态常量a和控制变量初始?;然后Lua调用iter(a,0)q回1,
a[1](除非a[1]=nil);W二ơP代调用iter(a,1)q回2,a[2]...直到W一个非nil元素. Lua库中实现的pairs是一个用next实现的原始方? function pairs (t) return next, t, nil end q可以不使用ipairs直接使用next for k, v in next, t do ... end C:exp-listq回l果会被调整Z?所以Lua获取next,t,nil;切地说当他调用pairs时获? 7.4 多状态的q代?br />
很多情况?q代器需要保存多个状态信息而不是简单的状态常量和控制变量,最单的Ҏ是用闭?q有一U方法就是将所有的状态信息封装到table
?table作ؓq代器的状态常?因ؓq种情况下可以将所有的信息存放在table?所以P代函数通常不需要第二个参数. 下面我们重写allwordsq代?q一ơ我们不是用闭包而是使用带有两个?line,pos)的table. 开始P代的函数是很单的,他必返回P代函数和初始状? local iterator -- to be defined later
function allwords () local state = {line = io.read(), pos = 1} return iterator, state end
真正的处理工作是在P代函数内完成: 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 -- found a word? -- update next position (after this word) state.pos = e + 1 return string.sub(state.line, s, e) else -- word not found state.line = io.read() -- try next line... state.pos = 1 -- ... from first position end end return nil -- no more lines: end loop end
我们应该可能的写无状态的q代?因ؓq样循环的时候由for来保存状?不需要创建对象花费的代h?如果不能用无状态的q代器实?应尽可能使用?
?可能不要用tableq种方式,因ؓ创徏闭包的代仯比创建table?另外Lua处理闭包要比处理table速度快些.后面我们q将看到另一
U用协同来创徏q代器的方式,q种方式功能更强但更复杂. 7.4 真正的P代器 q代器的名字有一些误?因ؓ它ƈ没有q代,完成q代功能的是for语句,也许更好的叫法应该是'生成?;但是在其他语a比如java,C++q代器的说法已经很普遍了,我们也将沿用q种术语. 有一U方式创Z个在内部完成q代的P代器.q样当我们用P代器的时候就不需要用@环了;我们仅仅使用每一ơP代需要处理的d作ؓ参数调用q代器即?具体地说,q代器接受一个函C为参?q且q个函数在P代器内部被调? 作ؓ一个具体的例子,我们使用上述方式重写allwordsq代? function allwords (f) -- repeat for each line in the file for l in io.lines() do -- repeat for each word in the line for w in string.gfind(l, "%w+") do -- call the function f(w) end end end 如果我们惌打印出单?只需?br /> allwords(print) 更一般的做法是我们用匿名函CZ为参?下面的例子打印出单词'hello'出现的次? local count = 0 allwords(function (w) if w == "hello" then count = count + 1 end end) print(count) 用forl构完成同样的Q? local count = 0 for w in allwords() do if w == "hello" then count = count + 1 end end print(count) 真正的P代器风格的写法在Lua老版本中很流?那时q没有for循环. 两种风格的写法相差不?但也有区?一斚w,W二U风格更Ҏ书写和理?另一斚w,forl构更灵z?可以使用break和continue语句 ;在真正的q代器风格写法中return语句只是从匿名函Cq回而不是退出@?
-- 创徏一个新的协E?br /> function coro.create(f) return coroutine.wrap(function(val) return nil,f(val) end) end
-- 把控制权及指定的数据val传给协程k function coro.transfer(k,val) if coro.current ~= coro.main then return coroutine.yield(k,val) else -- 控制权分z@?br /> while k do coro.current = k if k == coro.main then return val end k,val = k(val) end error("coroutine ended without transfering control...") end end
function foo1(n) print("1: foo1 received value "..n) n = coro.transfer(foo2,n + 10) print("2: foo1 received value "..n) n = coro.transfer(coro.main,n + 10) print("3: foo1 received value "..n) coro.transfer(coro.main,n + 10) end
function foo2(n) print("1: foo2 received value "..n) n = coro.transfer(coro.main,n + 10) print("2: foo2 received value "..n) coro.transfer(foo1,n + 10) end
function main() foo1 = coro.create(foo1) foo2 = coro.create(foo2) local n = coro.transfer(foo1,0) print("1: main received value "..n) n = coro.transfer(foo2,n + 10) print("2: main received value "..n) n = coro.transfer(foo1,n + 10) print("3: main received value "..n) end
--把main设ؓd?协程) coro.main = main --coro.main设ؓ当前协程 coro.current = coro.main --开始执行主函数(协程) coro.main()
1: foo1 received value 0 1: foo2 received value 10 1: main received value 20 2: foo2 received value 30 2: foo1 received value 40 2: main received value 50 3: foo1 received value 60 3: main received value 70
Q逻辑q算 and, or, not 其中Qand ?or 与C语言区别特别大?br /> 在这里,请先CQ在Lua中,只有false和nil才计ؓfalseQ其它Q何数据都计算为trueQ?也是trueQ?br /> and ?or的运结果不是true和falseQ而是和它的两个操作数相关?br /> a and bQ如果a为falseQ则q回aQ否则返回b a or bQ如?a 为trueQ则q回aQ否则返回b
丑և个例子: print(4 and 5) --> 5 print(nil and 13) --> nil print(false and 13) --> false print(4 or 5) --> 4 print(false or 5) --> 5
在Lua中这是很有用的特性,也是比较令hh的特性?br /> 我们可以模拟C语言中的语句Qx = a? b : cQ在Lua中,可以写成Qx = a and b or c?br /> 最有用的语句是Q?x = x or vQ它相当于:if not x then x = v end ?br /> Q运符优先U,从高C序如下Q?br /> ^ not - Q一元运) * / + - ..Q字W串q接Q?br /> < > <= >= ~= == and or
III. 关键?br /> 关键字是不能做ؓ变量的。Lua的关键字不多Q就以下几个Q?br /> and break do else elseif end false for function if in local nil not or repeat return then true until while
IV. 变量cd 怎么定一个变量是什么类型的呢?大家可以用type()函数来检查。Lua支持的类型有以下几种Q?br /> Nil I|所有没有用过的变量,都是nil。nil既是|又是cd?br /> Boolean 布尔?br /> Number 数|在Lua里,数值相当于C语言的double String 字符Ԍ如果你愿意的话,字符串是可以包含'\0'字符?br /> Table 关系表类型,q个cd功能比较强大Q我们在后面慢慢说?br /> Function 函数cdQ不要怀疑,函数也是一U类型,也就是说Q所有的函数Q它本n是一个变量?br /> Userdata 嗯,q个cd专门用来和Lua的宿L交道的。宿主通常是用C和C++来编写的Q在q种情况下,Userdata可以是宿ȝL数据cdQ常用的有Struct和指针?br /> Thread U程cdQ在Lua中没有真正的U程。Lua中可以将一个函数分成几部䆾q行。如果感兴趣的话Q可以去看看Lua的文?br /> V. 变量的定?br /> 所有的语言Q都要用到变量。在Lua中,不管你在什么地方用变量,都不需要声明,q且所有的q些变量L全局变量Q除非,你在前面加上"local"?br /> q一点要特别注意Q因Z可能惛_函数里用局部变量,却忘了用local来说明?br /> 至于变量名字Q它是大写相关的。也是_A和a是两个不同的变量?br /> 定义一个变量的Ҏ是赋倹{?Q?操作是用来赋值的 我们一h定义几种常用cd的变量吧?br /> A. Nil 正如前面所说的Q没有用过的变量的|都是Nil。有时候我们也需要将一个变量清除,q时候,我们可以直接l变量赋以nil倹{如Q?br /> var1=nil -- h?nil 一定要写
B. Boolean
布尔值通常是用在进行条件判断的时候。布值有两种Qtrue ?
false。在Lua中,只有false和nil才被计算为falseQ而所有Q何其它类型的|都是true。比?Q空串等{,都是true。不要被
C语言的习惯所误导Q?在Lua中的的确是true。你也可以直接给一个变量赋以Booleancd的|如: varboolean = true
C. Number 在Lua中,是没有整数类型的Q也不需要。一般情况下Q只要数g是很大(比如不超q?00,000,000,000,000Q,是不会生舍入误差的。在很多CPU上,实数的运ƈ不比整数慢?br /> 实数的表C方法,同C语言cMQ如Q?br /> 4 0.4 4.57e-3 0.3e12 5e+20
D. String 字符ԌL一U非常常用的高cd。在Lua中,你可以非常方便的定义很长很长的字W串?br /> 字符串在Lua中有几种Ҏ来表C,最通用的方法,是用双引h单引h括v一个字W串的,如: "This is a string." 和C语言相同的,它支持一些{义字W,列表如下Q?br /> \a bell \b back space \f form feed \n newline \r carriage return \t horizontal tab \v vertical tab \\ backslash \" double quote \' single quote \[ left square bracket \] right square bracket
׃q种字符串只能写在一行中Q因此,不可避免的要用到转义字符。加入了转义字符的串Q看h实在是不敢恭l_比如Q?br /> "one line\nnext line\n\"in quotes\", 'in quotes'" 一大堆?\"W号让h看v来很倒胃口。如果你与我有同感,那么Q我们在Lua中,可以用另一U表C方法:?[["?]]"多行的字符串括hQ如Q?br /> page = [[ <HTML> <HEAD> <TITLE>An HTML Page</TITLE> </HEAD> <BODY> <A HREF="http://www.lua.org">Lua</A> [[a text between double brackets]] </BODY> </HTML> ]]
值得注意的是Q在q种字符串中Q如果含有单独用的"[["?]]"׃然得?\["?\]"来避免歧义。当Ӟq种情况是极会发生的?br /> E. Table
关系表类型,q是一个很强大的类型。我们可以把q个cd看作是一个数l。只是C语言的数l,只能用正整数来作索引Q在Lua中,你可以用Lcd?
作数l的索引Q除了nil。同P在C语言中,数组的内容只允许一U类型;在Lua中,你也可以用Q意类型的值来作数l的内容Q除了nil?br /> Table的定义很单,它的主要特征是用"{"?}"来括起一pd数据元素的。比如:
G. Userdata ?Thread q两个类型的话题Q超Z本文的内容,׃打算l说了?br /> VI. l束?br /> p么结束了吗?当然不是Q接下来Q需要用Lua解释器,来帮助你理解和实践了。这小文只是帮助你大体了解Lua的语法。如果你有编E基Q相信会很快对Lua上手了?br /> pC语言一PLua提供了相当多的标准函数来增强语言的功能。用这些标准函敎ͼ你可以很方便的操作各U数据类型,q处理输入输出。有兌斚w的信息,你可以参考《Programming in Lua 》一书,你可以在|络上直接观看电子版Q网址为:http://www.lua.org/pil/index.html 当然QLua的最强大的功能是能与宿主E序亲蜜无间的合作,因此Q下一文章,我会告诉大家Q如何在你的E序中用Lua语言作ؓ脚本Q你的E序和Lua脚本q行交互?br />--------------------------------------------------------------------------------------------------------- 使用程 1. 函数的?br />以下E序演示了如何在Lua中用函? 及局部变?br />例e02.lua -- functions function pythagorean(a, b) local c2 = a^2 + b^2 return sqrt(c2) end print(pythagorean(3,4))
q行l果 5
E序说明 在Lua中函数的定义格式? function 函数?参数) ... end 与Pascal语言不同, end不需要与begin配对, 只需要在函数l束后打个end可以了. 本例函数的作用是已知直角三角形直角边, 求斜辚w? 参数a,b分别表示直角辚w, 在函数内定义了local形变量用于存储斜边的qx. 与C语言相同, 定义在函数内的代 码不会被直接执行, 只有ȝ序调用时才会被执? local表示定义一个局部变? 如果不加local刚表Cc2Z个全局变量, local的作用域 是在最里层的end和其配对的关键字之间, 如if ... end, while ... end{。全局变量?br />作用域是整个E序?br /> 2. 循环语句 例e03.lua -- Loops for i=1,5 do print("i is now " .. i) end
q行l果 i is now 1 i is now 2 i is now 3 i is now 4 i is now 5
E序说明 q里偶们用到了for语句 for 变量 = 参数1, 参数2, 参数3 do 循环?br />end 变量以参数3为步? 由参?变化到参? 例如: for i=1,f(x) do print(i) end for i=10,1,-1 do print(i) end
q里print("i is now " .. i)中,偶们用到?.Q这是用来连接两个字W串的, 偶在(1)的试试看中提到的Q不知道你们{对了没有?br />虽然q里i是一个整型量QLua在处理的时候会自动转成字符串型Q不需偶们费心?br /> 3. 条g分支语句 例e04.lua -- Loops and conditionals for i=1,5 do print(“i is now ?.. i) if i < 2 then print(“small? elseif i < 4 then print(“medium? else print(“big? end end
q行l果 i is now 1 small i is now 2 medium i is now 3 medium i is now 4 big i is now 5 big
E序说明 if else用法比较? cM于C语言, 不过此处需要注意的是整个if只需要一个end, 哪怕用了多个elseif, 也是一个end. 例如 if op == "+" then r = a + b elseif op == "-" then r = a - b elseif op == "*" then r = a*b elseif op == "/" then r = a/b else error("invalid operation") end
Lua对Table占用内存的处理是自动? 如下面这D代?br />a = {} a["x"] = 10 b = a -- `b' refers to the same table as `a' print(b["x"]) --> 10 b["x"] = 20 print(a["x"]) --> 20 a = nil -- now only `b' still refers to the table b = nil -- now there are no references left to the table b和a都指向相同的table, 只占用一块内? 当执行到a = nil? b仍然指向table, 而当执行到b=nil? 因ؓ没有指向table的变量了, 所以Lua会自动释放table所占内?br /> 3.Table的嵌?br />Table的用还可以嵌套Q如下例 例e06.lua -- Table ‘constructor? myPolygon = { color=“blue? thickness=2, npoints=4; {x=0, y=0}, {x=-10, y=0}, {x=-5, y=4}, {x=0, y=4} }
-- Print the color print(myPolygon[“color”])
-- Print it again using dot -- notation print(myPolygon.color)
-- The points are accessible -- in myPolygon[1] to myPolygon[4]
-- Print the second point’s x -- coordinate print(myPolygon[2].x)
所以我们需要一个@环嵌套来搜烦Q?br /> 以下假设我们w上都是16格的包: for bag=0,4,1 do --包的~号Z叛_左,0,1,2,3,4 for cw=1,16,1 do --槽位的编号ؓ上到下,左到?1,2,3,4,5......16 .............. --q里我们可以写如判断物品是否为我们需要的东西的语?br />end --表示内@环结? end --外@环结?br />
/script bag=0 cw=1 sc=1 --定义好变量,bag是包的编Pcw表示查找包的槽位Qsc指向最后一个包内的槽位 for bag=0,3,1 do --?号包开始,?号包l束Q最后一个包不搜索?br />for cw=1,16,1 do --q里假设所有的包都?6个槽位的Q如果没那么多槽位的包也可以用?br />if GetContainerItemLink(bag,cw)~=nil --判断q个槽位是否是空的,是空q接蟩C一个槽?br /> then if string.find(GetContainerItemInfo(bag,cw),"Gem") --判断q个槽位里是否是灵魂片QGem为灵碎片的关键?br /> then while string.find(GetContainerItemInfo(4,sc),"Gem") do sc=sc+1 end --q是一个小循环Q用于判断最后一个包里原来是否已l有灵魂片Q有的话指向包的下一个槽?br /> PickupContainerItem(bag,cw) PickupContainerItem(4,sc) PickupContainerItem(bag,cw) --q?句控制灵碎片和最后一个包内物品的交换 sc=sc+1 --重要Q不能忘记这个,每放|好一个碎片后p把最后一个包?br /> 槽位指针指向下一个槽位,上面的小循环是无法判断刚刚放好的片的?br /> end end end end -循环l束
/script function P(c,d) PickupContainerItem(c,d) end /script
function I(e,f) if GetContainerItemInfo(e,f) then return
string.find(GetContainerItemInfo(e,f),"Gem") else return nil end end
原来的宏变成了Q?br /> /script bag=0 cw=1 sc=1 for bag=0,3,1 do for cw=1,16,1 do if G(bag,cw)~=nil then if I(bag,cw) then while I(4,sc) do sc=sc+1 end P(bag,cw) P(4,sc) P(bag,cw) sc=sc+1 end end end end
多余的变量定义和q长的变量都可以更改Q?br />
/script s=1 for g=0,3 do for w=1,16 do if G(g,w) then if I(g,w) then while I(4,s) do s=s+1 end P(g,w) P(4,s) P(g,w) s=s+1 end end end end