小结(两个Lua程序示例)

最后更新于:2022-04-01 20:44:22

本篇文章作为Lua基础部分的一个小结,演示两个小程序,来表现Lua的不同特性。第一个例子说明Lua如何作为一门数据描述性语言使用。第2个例子,是一个马尔可夫链算法的实现。 **ps**:个人觉得书中的这一章有点莫名其妙,感觉两个例子也没有起到什么总结作用,反而感觉讲得有点云里雾里的。 ## 1. 数据描述 在Lua的网站上保留了一个数据库,存储了世界上使用Lua的项目的一些示例代码。我们用一个结构体来表示数据库中的每一个条目,如下所示: ~~~ entry{ title = "Tecgraf", org = "Computer Graphics Technology Group, PUC-Rio", url = "http://www.tecgraf.puc-rio.br/", contact = "Waldemar Celes", description = [[ Tecgraf is the result of a partnership between PUC-Rio, the Pontifical Catholic University of Rio de Janeiro, and PETROBRAS, the Brazilian Oil Company. Tecgraf is Lua's birthplace, and the language has been used there since 1993. Currently, more than thirty programmers in Tecgraf use Lua regularly; they have written more than two hundred thousand lines of code, distributed among dozens of final products.]] } ~~~ 含有一系列这样条目的一个数据文件,居然也是一个Lua程序,它以table为参数去对函数*entry* 进行一系列调用。 我们要写一个程序将这些数据以HTML格式展示出来,这些数据就变成网页 http://www.lua.org/uses.html。 因为有很多项目,最终的页面先列出所有项目的主题,再展示每个项目的细节。如下所示,是程序的一个典型输出: ~~~ Projects using Lua Here are brief descriptions of some projects around the world that use Lua.

Tecgraf
Computer Graphics Technology Group, PUC-Rio

Tecgraf is the result of a partnership between ... distributed among dozens of final products.

Contact: Waldemar Celes


~~~ 为了读取数据,程序简单定义了*entry* ,然后用*dofile* 运行该数据文件。注意,我们必须遍历所有的条目两遍,第一次是为了获取主题列表,第二次来获取项目描述信息。一种方法是将所有的条目手收集到一个array中。但是,还有另一个比较吸引人的方法:运行这个数据文件两次,每次使用不同的*entry* 定义。下面我们使用第二种方法。 首先,我们定义一个格式化写入的函数: ~~~ function fwrite (fmt, ...) return io.write(string.format(fmt, ...)) end ~~~ 函数*writeheader* 简单的写入page header,如下: ~~~ function writeheader() io.write([[ Projects using Lua Here are brief descriptions of some projects around the world that use Lua.
]]) end ~~~ *entry* 的第一个定义,将每一个项目主题写入到list中为一个条目,参数*o*  是描述项目的table: ~~~ function entry1 (o) count = count + 1 local title = o.title or '(no title)' fwrite('
  • %s\n', count, title) end ~~~ 如果*o.title* 为**nil**(也就是说这个域没有被提供),函数使用一个固定的"(no title)"。 *entry* 的第二个定义如下,写入一个项目的所有有用数据。有一点复杂,因为所有的选项都是可选的。(HTML中使用双引号,为了避免跟HTML冲突,我们在程序中使用单引号)。 ~~~ function entry2 (o) count = count + 1 fwrite('
    \n

    \n') local href = o.url and string.format(' href="%s"', o.url) or '' local title = o.title or o.org or 'org' fwrite('%s\n', count, href, title) if o.title and o.org then fwrite('
    \n%s', o.org) end fwrite('\n

    \n') if o.description then fwrite('%s

    \n', string.gsub(o.description, '\n\n+', '

    \n')) end if o.email then fwrite('Contact: %s\n', o.email, o.contact or o.email) elseif o.contact then fwrite('Contact: %s\n', o.contact) end end ~~~ 最后一个函数*writetail* ,写page tail。 ~~~ function writetail () fwrite('\n') end ~~~ 主程序如下所示。程序打开页面,加载数据文件,用*entry* 的第一个定义(entry1)来创建主题列表,然后重置计数器,再用*entry* 的第二个定义(entry2)来运行数据文件,最后关闭页面。 ~~~ local inputfile = 'db.lua' writeheader() count = 0 f = loadfile(inputfile) -- loads data file entry = entry1 -- defines 'entry' fwrite('

      \n') f() -- runs data file fwrite('
    \n') count = 0 entry = entry2 -- redefines 'entry' f() -- runs data file again writetail() ~~~ 汇总了一下上面的程序代码如下: ~~~ function fwrite (fmt, ...) return io.write(string.format(fmt, ...)) end function writeheader() io.write([[ Projects using lua Here are brief description of some projects around the world that use Lua.
    ]]) end function entry1 (o) count = count + 1 local title = o.title or '(no title)' fwrite('
  • %s\n', count, title) end function entry2 (o) count = count + 1 fwrite('
    \n

    \n') local href = o.url and string.format(' href="%s"', o.url) local title = o.title or o.org or 'org' fwrite('%s\n', count, href, title) if o.title and o.org then fwrite('
    \n%s', o.org) end fwrite('\n

    \n') if o.description then fwrite('%s

    \n', string.gsub(o.description, '\n\n+', '

    \n')) end if o.email then fwrite('Contact: %s\n', o.email, o.contact or o.email) elseif o.contact then fwrite('Contact: %s\n', o.contact) end end function writetail () fwrite('\n') end local inputfile = 'db.lua' writeheader() count = 0 f = loadfile(inputfile) -- loads data file entry = entry1 -- defines 'entry' fwrite('

      \n') f() -- runs data file fwrite('
    \n') count = 0 entry = entry2 -- redefines 'entry' f() -- runs data file again writetail() ~~~ db.lua文件的内容如下: ~~~ entry{ title = "Tecgraf", org = "Computer Graphics Technology Group, PUC-Rio", url = "http://www.tecgraf.puc-rio.br/", contact = "Waldemar Celes", description = [[ TeCGraf is the result of a partnership between PUC-Rio, the Pontifical Catholic University of Rio de Janeiro, and PETROBRAS, the Brazilian Oil Company. TeCGraf is Lua's birthplace, and the language has been used there since 1993. Currently, more than thirty programmers in TeCGraf use Lua regularly; they have written more than two hundred thousand lines of code, distributed among dozens of final products.]] } entry{ title = "Tecgraf_02", org = "Computer Graphics Technology Group, PUC-Rio, the 2nd entry", url = "http://www.tecgraf.puc-rio.br/, the 2nd entry", contact = "Waldemar Celes 02", description = [[ This is the 2nd entry, TeCGraf is the result of a partnership between PUC-Rio, the Pontifical Catholic University of Rio de Janeiro, and PETROBRAS, the Brazilian Oil Company. TeCGraf is Lua's birthplace, and the language has been used there since 1993. Currently, more than thirty programmers in TeCGraf use Lua regularly; they have written more than two hundred thousand lines of code, distributed among dozens of final products.]] } ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef2a654a.jpg) ## 2. 马尔可夫链算法 第2个例子是马尔可夫链算法的实现.这个程序基于文本中的前n个词来生成随机的文本,这里我们假设n为2。 程序的第一部分,读取基本文本,并创建一个table,每两个单词为一个前缀,将基本文本中在该前缀之后的单词(可能有多个)存入table中。创建完该table后,程序用这个table去随机生成文本,每个前缀后的单词出现的概率跟在基本文本中大致相同。这样,我们就得到一个相当随机的文本。 我们会把两个单词用一个空格“ ”链接起来,编码为前缀: ~~~ function prefix (w1, w2) return w1 .. " " .. w2 end ~~~ 我们使用字符串 NOWORD("\n")来初始化前缀单词,并且标记文本的结尾。例如: ~~~ the more we try the more we do ~~~ 生成的table将会是: ~~~ { ["\n \n"] = {"the"}, ["\n the"] = {"more"}, ["the more"] = {"we", "we"}, -- 有两处"the more we" ["more we"] = {"try", "do"}, -- 两处"more we try", "more we do" ["we try"] = {"the"}, ["try the"] = {"more"}, ["we do"] = {"\n"}, } ~~~ 程序将它的table保存到变量statetab中。我们用下面的函数在这个table的前缀list中插入一个新的单词 ~~~ function insert (index, value) local list = statetab[index] if list == nil then statetab[index] = {value} else list[#list + 1] = value end end ~~~ 它首先检查这个前缀是否有list了;如果么有,那么用这个新值创建一个新的list;否则,就将这个新值插入到已存在的list的末尾。 要创建statetab这个table,我们保存两个变量,w1 和 w2,保存最后读取的两个单词。每读取一个新的单词,我们就将它添加到与w1-w2关联的list中,然后update一下w1和w2。 创建完这个table后,程序开始用MAXGEN个单词来生成文本。首先,它重新初始化w1和w2。然后,对每个前缀,它从合法的下一个单词的list中随机选取一个,打印这个单词,然后update下w1和w2.   下面是完整版的程序。 ~~~ -- Auxiliary definitions for the Markov program 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 -- update next position 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 function prefix (w1, w2) return w1 .. " " .. w2 end local statetab = {} function insert (index, value) local list = statetab[index] if list == nil then statetab[index] = {value} else list[#list + 1] = value end end -- The Markov program local N = 2 local MAXGEN = 10000 local NOWORD = "\n" -- build table local w1, w2 = NOWORD, NOWORD for w in allwords() do insert(prefix(w1, w2), w) w1 = w2; w2 = w; end insert(prefix(w1, w2), NOWORD) -- generate text w1 = NOWORD; w2 = NOWORD -- reinitialize for i=1, MAXGEN do local list = statetab[prefix(w1, w2)] -- choose a random item from list local r = math.random(#list) local nextword = list[r] if nextword == NOWORD then return end io.write(nextword, " ") w1 = w2; w2 = nextword end ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef2c7a07.jpg) 水平有限,如果有朋友发现错误,欢迎留言交流
  • ';

    安装LuaSocket

    最后更新于:2022-04-01 20:44:20

    这里为《[Lua基础 coroutine —— Lua的多线程编程](http://blog.csdn.net/wzzfeitian/article/details/8832017)》做一下准备工作,因为用到了socket库,这里就说明一下怎么在fedora上安装luasocket,以防有的朋友的开发环境跟博主的一样,默认没有该库,又得自己到处去查怎么安装。 note:该库从文档看,好像只支持lua5.1,博主没有尝试过5.2,有兴趣的可以试一下是否可以。 首先去这个网站[http://w3.impa.br/~diego/software/luasocket/old/](http://w3.impa.br/~diego/software/luasocket/old/)下载源码,下载那个2.0.3就可以了,博主是用的这个,将其放到你的home目录(或者有写权限的其他目录都可以),用tar -zxvf filename 解压缩。 进入luasocket目录,有个config文件,打开这个文件,有2处为止要改: INTALL_TOP_SHARE 和 INTALL_TOP_LIB两个变量,从opt目录改为usr目录,因为默认的环境变量设置为从usr下面的一些目录搜索.h文件和库文件, 修改后为: ~~~ INSTALL_TOP_SHARE=/usr/local/share/lua/5.1 INSTALL_TOP_LIB=/usr/local/lib/lua/5.1 ~~~ 下面的compiler and linker setting设置需要修改,因为博主这里看到是默认平台是MAC OS,将“for Mac OS X”下面的几行注释掉,将“for linux”下面几行打开,如下: ~~~ #------ # Compiler and linker settings # for Mac OS X # #CC=gcc #DEF= -DLUASOCKET_DEBUG -DUNIX_HAS_SUN_LEN #CFLAGS= $(LUAINC) $(COMPAT) $(DEF) -pedantic -Wall -O2 -fno-common #LDFLAGS=-bundle -undefined dynamic_lookup #LD=export MACOSX_DEPLOYMENT_TARGET="10.3";gcc #------ # Compiler and linker settings # for Linux CC=gcc DEF=-DLUASOCKET_DEBUG CFLAGS= $(LUAINC) $(DEF) -pedantic -Wall -O2 -fpic LDFLAGS=-O -shared -fpic LD=gcc ~~~ 然后保存退出。 运行 ~~~ make sudo make install ~~~ 如果没有其他错误的话,就安装成功了,此时运行下require "socket",没有错误提示了,大功告成。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef28f144.jpg) 水平有限,如果有朋友发现错误,欢迎留言交流
    ';

    Lua的多线程编程

    最后更新于:2022-04-01 20:44:18

    Lua的*coroutine* 跟*thread* 的概念比较相似,但是也不完全相同。一个multi-thread的程序,可以同时有多个thread 在运行,但是一个multi-coroutines的程序,同一时间只能有一个*coroutine* 在运行,而且当前正在运行的*coroutine* 只有在被显式地要求挂起时,才会挂起。Lua的*coroutine* 是一个强大的概念,尽管它的几个主要应用都比较复杂。 ## 1. Coroutine 基础 Lua将coroutine相关的所有函数封装在表coroutine 中。*create* 函数,创建一个coroutine ,以该coroutine 将要运行的函数作为参数,返回类型为thread 。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef13150c.PNG) coroutine 有4个不同的状态:*suspended, running, dead, normal*。当新*create* 一个coroutine的时候,它的状态为*suspended* ,意味着在create 完成后,该*coroutine* 并没有立即运行。我们可以用函数status 来查看该coroutine 的状态: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef14579d.PNG) 函数*coroutine.resume* (恢复)运行该coroutine,将其状态从suspended变为running: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef15de6d.PNG) 在该示例中,该coroutine运行,简单地输出一个“hi”就结束了,该coroutine变为dead状态: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef172916.PNG) 到目前为止,coroutine看起来好像也就这么回事,类似函数调用,但是更复杂的函数调用。但是,coroutine的真正强大之处在于它的*yield* 函数,它可以将正在运行的coroutine 挂起,并可以在适当的时候再重新被唤醒,然后继续运行。下面,我们先看一个简单的示例: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef18a907.PNG) 我们一步一步来讲,该coroutine每打印一行,都会被挂起,看起来是不是在运行*yield* 函数的时候被挂起了呢?当我们用*resume* 唤醒该coroutine时,该coroutine继续运行,打印出下一行。直到最后没有东西打印出来的时候,该coroutine退出循环,变为dead状态(注意最后那里的状态变化)。如果对一个dead状态的coroutine进行*resume* 操作,那么resume会返回false+err_msg,如上面最后两行所示。 注意,resume 是运行在protected mode下。当coroutine内部发生错误时,Lua会将错误信息返回给*resume* 调用。 当一个coroutine A在resume另一个coroutine B时,A的状态没有变为suspended,我们不能去resume它;但是它也不是running状态,因为当前正在running的是B。这时A的状态其实就是normal 状态了。 Lua的一个很有用的功能,**resume-yield**对,可以用来交换数据。下面是4个小示例: 1)main函数中没有*yield*,调用*resume*时,多余的参数,都被传递给main函数作为参数,下面的示例,1 2 3分别就是a b c的值了: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef1a7425.PNG) 2)main函数中有*yield*,所有被传递给yield的参数,都被返回。因此resume的返回值,除了标志正确运行的**true**外,还有传递给yield的参数值: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef1c3c8f.PNG) 3)yield也会把多余的参数返回给对应的resume,如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef1d9eec.PNG) 为啥第一个resume没有任何输出呢?我的答案是,yield没有返回,print就根本还没运行。 4)当一个coroutine结束的时候,main函数的所有返回值都被返回给resume: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef1ee91a.PNG) 我们在同一个coroutine中,很少会将上面介绍的这些功能全都用上,但是所有这些功能都是很useful的。 目前为止,我们已经了解了Lua中coroutine的一些知识了。下面我们需要明确几个概念。Lua提供的是asymmetric coroutine,意思是说,它需要一个函数(yield)来挂起一个coroutine,但需要另一个函数(resume)来唤醒这个被挂起的coroutine。对应的,一些语言提供了symmetric coroutine,用来切换当前coroutine的函数只有一个。 有人想把Lua的coroutine称为semi-coroutine,但是这个词已经被用作别的意义了,用来表示一个被限制了一些功能来实现出来的coroutine,这样的coroutine,只有在一个coroutine的调用堆栈中,没有剩余任何挂起的调用时,才会被挂起,换句话说,就是只有main可以挂起。Python中的generator好像就是这样一个类似的semi-coroutine。 跟asymmetric coroutine和symmetric coroutine的区别不同,coroutine和generator(Python中的)的不同在于,generator并么有coroutine的功能强大,一些用coroutine可实现的有趣的功能,用generator是实现不了的。Lua提供了一个功能完整的coroutine,如果有人喜欢symmetric coroutine,可以自己简单的进行一下封装。 ## 2. pipes和filters couroutine的一个典型的例子就是producer-consumer问题。我们来假设有这样两个函数,一个不停的produce一些值出来(例如从一个file中不停地读),另一个不断地consume这些值(例如,写入到另一个file中)。这两个函数的样子应该如下: ~~~ function producer () while true do local x = io.read() -- produce new value send(x) -- send to consumer end end function consumer () while true do local x = receive() -- receive from producer io.write(x, "\n") -- consume new value end end ~~~ 这两个函数都不停的在执行,那么问题来了,怎么来匹配send和recv呢?究竟谁先谁后呢? coroutine提供了解决上面问题的一个比较理想的工具resume-yield。我们还是不说废话,先看看代码再来说说我自己的理解: ~~~ function receive (prod) local status, value = coroutine.resume(prod) return value end function send (x) coroutine.yield(x) end function producer() return coroutine.create(function () while true do local x = io.read() -- produce new value send(x) end end) end function consumer (prod) while true do local x = receive(prod) -- receive from producer io.write(x, "\n") -- consume new value end end p = producer() consumer(p) ~~~ 程序先调用consumer, 然后recv函数去resume唤醒producer,produce一个值,send给consumer,然后继续等待下一次resume唤醒。看下下面的这个示例应该就很明白了: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef20ee45.PNG) 我们可以继续扩展一下上面的例子,增加一个filter,在producer和consumer之间做一些数据转换啥的。那么filter里都做些什么呢?我们先看一下没加filter之前的逻辑,基本就是producer去send,send to consumer,consumer去recv,recv from producer,可以这么理解吧。加了filter之后呢,因为filter需要对data做一些转换操作,因此这时的逻辑为,producer去send,send tofilter,filter去recv,recv from producer,filter去send,send to consumer,consumer去recv,recv fromfilter。红色的部分是跟原来不同的。此时的代码如下: ~~~ function send(x) coroutine.yield(x) end function producer() return coroutine.create(function () while true do local x = io.read() send(x) end end) end function consumer(prod) while true do local x = receive(prod) if x then io.write(x, '\n') else break end end end function filter(prod) return coroutine.create(function () for line = 1, math.huge do local x = receive(prod) x = string.format('%5d %s', line, x) send(x) end end) end p = producer() f = filter(p) consumer(f) ~~~ 看完上面的例子,你是否想起了unix中的pipe?coroutine怎么说也是multithreading的一种。使用pipe,每个task得以在各自的process里执行,而是用coroutine,每个task在各自的coroutine中执行。pipe在writer(producer)和reader(consumer)之间提供了一个buffer,因此相对的运行速度还是相当可以的。这个是pipe很重要的一个特性,因为process间通信,代价还是有点大的。使用coroutine,不同task之间的切换成本更小,基本上也就是一个函数调用,因此,writer和reader几乎可以说是齐头并进了啊。 ## 3. 用coroutine实现迭代器 我们可以把迭代器 循环看成是一个特殊的producer-consumer例子:迭代器produce,循环体consume。下面我们就看一下coroutine为我们提供的强大的功能,用coroutine来实现迭代器。 我们来遍历一个数组的全排列。先看一下普通的loop实现,代码如下: ~~~ function printResult(a) for i = 1, #a do io.write(a[i], ' ') end io.write('\n') end function permgen(a, n) n = n or #a if n <= 1 then printResult(a) else for i = 1, n do a[n], a[i] = a[i], a[n] permgen(a, n-1) a[n], a[i] = a[i], a[n] end end end permgen({1,2,3}) ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef22744a.jpg) 再看一下迭代器实现,注意比较下代码的改变的部分: ~~~ function printResult(a) for i = 1, #a do io.write(a[i], ' ') end io.write('\n') end function permgen(a, n) n = n or #a if n <= 1 then coroutine.yield(a) else for i = 1, n do a[n], a[i] = a[i], a[n] permgen(a, n-1) a[n], a[i] = a[i], a[n] end end end function permutations(a) local co = coroutine.create(function () permgen(a) end) return function () local code, res = coroutine.resume(co) return res end end for p in permutations({"a", "b", "c"}) do printResult(p) end ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef23a7e8.jpg) permutations 函数使用了一个Lua中的常规模式,将在函数中去resume一个对应的coroutine进行封装。Lua对这种模式提供了一个函数coroutine.wap 。跟create 一样,wrap 创建一个新的coroutine ,但是并不返回给coroutine,而是返回一个函数,调用这个函数,对应的coroutine就被唤醒去运行。跟原来的resume 不同的是,该函数不会返回errcode作为第一个返回值,一旦有error发生,就退出了(类似C语言的assert)。使用wrap, permutations可以如下实现: ~~~ function permutations (a) return coroutine.wrap(function () permgen(a) end) end ~~~ wrap 比create 跟简单,它实在的返回了我们最需要的东西:一个可以唤醒对应coroutine的函数。 但是不够灵活。没有办法去检查wrap 创建的coroutine的status, 也不能检查runtime-error(没有返回errcode,而是直接assert)。 ## 4. 非抢占式多线程 从我们前面所写的可以看到,coroutine运行一系列的协作的多线程。每个coroutine相当于一个thread。一个yield-resume对可以在不同的thread之间切换控制权。但是,跟常规的multithr不同,coroutine是非抢占式的。一个coroutine在运行的时候,不可能被其他的coroutine从外部将其挂起,只有由其本身显式地调用yield才会挂起,并交出控制权。对一些程序来说,这没有任何问题,相反,因为非抢占式的缘故,程序变得更加简单。我们不需要担心同步问题的bug,因为在threads之间的同步都是显式的。我们只需要保证在对的时刻调用yield就可以了。 但是,使用非抢占式multithreading,不管哪个thread调用了一个阻塞的操作,那么整个程序都会被阻塞,这是不能容忍的。由于这个原因,很多程序员并不认为coroutine可以替代传统的multithreading。但是,下面我们可以看到一个有趣的解决办法。 一个很典型的multithreading场景:通过http下载多个remote files。我们先来看下如何下载一个文件,这需要使用LuaSocket库,如果你的开发环境没有这个库的话,可以看下博主的另一篇文章[](http://blog.csdn.net/wzzfeitian/article/details/8866390)[Lua基础 安装LuaSocket](http://blog.csdn.net/wzzfeitian/article/details/8866390),了解下如何在Linux上安装LuaSocket. 下载一个file的lua代码如下: ~~~ require("socket") host = "www.w3.org" file = "/standards/xml/schema" c = assert(socket.connect(host, 80)) c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") -- 注意GET后和HTTP前面的空格 while true do local s, status, partial = c:receive(2^10) io.write(s or partial) if status == "closed" then break end end c:close() ~~~ 运行结果有点长,不方便截图,就不贴了。 现在我们就知道怎么下载一个文件了。现在回到前面说的下载多个remote files的问题。当我们接收一个remote file的时候,程序花费了大多数时间去等待数据的到来,也就是在receive函数的调用是阻塞。因此,如果能够同时下载所有的files,那么程序的运行速度会快很多。下面我们看一下如何用coroutine来模拟这个实现。我们为每一个下载任务创建一个thread,在一个thread没有数据可用的时候,就调用yield 将程序控制权交给一个简单的dispatcher,由dispatcher来唤醒另一个thread。下面我们先把之前的代码写成一个函数,但是有少许改动,不再将file的内容输出到stdout了,而只是间的的输出filesize。 ~~~ function download(host, file) local c = assert(socket.connect(host, 80)) local count = 0 -- counts number of bytes read c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do local s, status, partial = receive(c) count = count + #(s or partial) if status == "closed" then break end end c:close() print(file, count) end ~~~ 上面代码中有个函数receive ,相当于下载单个文件中的实现如下: ~~~ function receive (connection) return connection:receive(2^10) end ~~~ 但是,如果要同时下载多文件的话,这个函数必须非阻塞地接收数据。在没有数据接收的时候,就调用yield挂起,交出控制权。实现应该如下: ~~~ function receive(connection) connection:settimeout(0) -- do not block local s, status, partial = connection:receive(2^10) if status == "timeout" then coroutine.yield(connection) end return s or partial, status end ~~~ settimeout(0)将这个连接设为非阻塞模式。当status变为“timeout”时,意味着该操作还没完成就返回了,这种情况下,该thread就yield。传递给yield的non-false参数,告诉dispatcher该线程仍然在运行。注意,即使timeout了,该连接还是会返回它已经收到的东西,存在partial变量中。 下面的代码展示了一个简单的dispatcher。表threads保存了一系列的运行中的thread。函数get 确保每个下载任务都单独一个thread。dispatcher本身是一个循环,不断的遍历所有的thread,一个一个的去resume。如果一个下载任务已经完成,一定要将该thread从表thread中删除。当没有thread在运行的时候,循环就停止了。 最后,程序创建它需要的threads,并调用dispatcher。例如,从w3c网站下载四个文档,程序如下所示: ~~~ require "socket" function receive(connection) connection:settimeout(0) -- do not block local s, status, partial = connection:receive(2^10) if status == "timeout" then coroutine.yield(connection) end return s or partial, status end function download(host, file) local c = assert(socket.connect(host, 80)) local count = 0 -- counts number of bytes read c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do local s, status, partial = receive(c) count = count + #(s or partial) if status == "closed" then break end end c:close() print(file, count) end threads = {} -- list of all live threads function get(host, file) -- create coroutine local co = coroutine.create(function () download(host, file) end) -- intert it in the list table.insert(threads, co) end function dispatch() local i = 1 while true do if threads[i] == nil then -- no more threads? if threads[1] == nil then -- list is empty? break end i = 1 -- restart the loop end local status, res = coroutine.resume(threads[i]) if not res then -- thread finished its task? table.remove(threads, i) else i = i + 1 end end end host = "www.w3.org" get(host, "/TR/html401/html40.txt") get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf") get(host, "/TR/REC-html32.html") get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt") dispatch() -- main loop ~~~ 我的程序运行了10s左右,4个文件已经下载完成,运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef24e6ff.jpg) 我又重新用阻塞式的顺序下载重试了一下,需要时间12s多一点,可能文件比较小,也不够多,对比不是很明显,阻塞的多文件下载代码如下,其实就是上面几段代码放在一块了 ~~~ function receive (connection) return connection:receive(2^10) end function download(host, file) local c = assert(socket.connect(host, 80)) local count = 0 -- counts number of bytes read c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do local s, status, partial = receive(c) count = count + #(s or partial) if status == "closed" then break end end c:close() print(file, count) end require "socket" host = "www.w3.org" download(host, "/TR/html401/html40.txt") download(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf") download(host, "/TR/REC-html32.html") download(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt") ~~~ 运行结果如下,跟上面的非阻塞式有点不同,下载完成的顺序,就是代码中写的顺序: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef26206a.jpg) 既然速度没有明显的更快,那么有没有优化空间呢,答案是,有。当没有thread有数据接收时,dispatcher遍历了每一个thread去看它有没有数据过来,结果这个过程比阻塞式的版本多耗费了30倍的cpu。 为了避免这个情况,我们使用LuaSocket提供的select函数。它运行程序在等待一组sockets状态改变时阻塞。代码改动比较少,在循环中,收集timeout的连接到表connections 中,当所有的连接都timeout了,dispatcher调用select 来等待这些连接改变状态。该版本的程序,在博主开发环境测试,只需7s不到,就下载完成4个文件,除此之外,对cpu的消耗也小了很多,只比阻塞版本多一点点而已。新的dispatch代码如下: ~~~ function dispatch() local i = 1 local connections = {} while true do if threads[i] == nil then -- no more threads? if threads[1] == nil then -- list is empty? break end i = 1 -- restart the loop connections = {} end local status, res = coroutine.resume(threads[i]) if not res then -- thread finished its task? table.remove(threads, i) else i = i + 1 connections[#connections + 1] = res if #connections == #threads then -- all threads blocked? socket.select(connections) end end end end ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef2793ba.jpg) 这边文章又是断断续续写了几天,文章的每个例子都是亲自运行过的,今天终于写完,养精蓄锐,明天开始去泰国旅行几天,希望有一个开心的行程。 水平有限,如果有朋友发现错误,欢迎留言交流
    ';

    编译、运行、错误处理

    最后更新于:2022-04-01 20:44:15

    尽管Lua是一门解析型的语言,但是在运行前也会被编译成某个中间状态。一门解析型的语言需要编译,这听起来有点不合常理。但是,实际上,解析型语言的与众不同,不是说它不需要编译,而是说它把编译作为其运行时的一部分,因此,它就可以执行各种来自外部的代码(例如网上的)。也许因为Lua中存在的如*dofile* 这样的函数,才使Lua可以被称为一门解析型语言。 1. 编译 之前我们介绍了*dofile* 来执行代码块,但是*dofile* 只是一个辅助函数。这里介绍一下*loadfile* 函数,它会从一个file中加载语句块,但是不运行;而是仅仅编译并作为一个函数返回。*loadfile* 不会像*dofile* 那样在运行时直接报错退出,而是返回错误码,这样我们就可以根据错误码做相应的处理。我们可以像下面这样定义*dofile* ,这也可以看出*dofile* 和*loadfile* 的区别 ~~~ function dofile (filename) local f = assert(loadfile(filename)) return f() end ~~~ 注意,*assert* 可以使*loadfile* 发生错误时,报错退出。 *dofile* 在处理有些简单的任务时,使用起来比较方便,只需要一次调用,它会完成所有的操作(编译,运行啥的)。但是,*loadfile*更加灵活。发生错误的时候,*loadfile* 会返回**nil**+ 'err_msg',我们可以根据实际情况对错误做出相应的处理。除此之外,如果想要运行一个file多次,可以先调用一次*loadfile* ,然后调用*loadfile* 返回的结果多次,就可以了。这比调用几次*dofile* 开销要小很多,因为*loadfile* 只会执行一次编译,而*dofile* 每次都用都要编译。 *loadstring* 跟*loadfile* 差不多,区别是从一个string中加载代码块,而不是从file中。例如: ~~~ f = loadstring("i = i + 1") ~~~ *f* 是*loadstring* 的返回值,应该是function类型, 调用的时候,会执行i = i + 1 :  ~~~ i = 0 f(); print(i) --> 1 f(); print(i) --> 2 ~~~ *loadstring* 函数功能非常强大,但是运行起来,开销也不小,并且有时会导致产生一些莫名其妙的代码。因此,在用*loadstring* 之前,先考虑下有没有更简单的办法。 下面这行代码,不太好看,但是很方便。(不鼓励这种调用方法) ~~~ loadstring(s)() ~~~ 如果有语法错误,*loadstring* 会返回**nil **+ 类似‘attempt to call a nil value’这样的err_msg。如果想获取更详细的err_msg,那就需要用*assert* : ~~~ assert(loadstring(s))() ~~~ 下面这样的使用方式(对一个字面值string使用loadstring),没什么意思, ~~~ f = loadstring("i = i + 1") ~~~ 粗略的等价于: ~~~ f = function () i = i + 1 end ~~~ 但是,也不是完全相同,且继续往下看。第二种方式的代码运行起来更快,因为它只需要编译一次,而*loadstring* 每次都需要编译。下面我们来看看,上面两段代码到底有什么不同,如下示例: ~~~ i = 32 local i = 0 f = loadstring("i = i + 1; print(i)") g = function () i = i + 1; print(i) end f() --> 33 g() --> 1 ~~~ *g* 函数处理的事局部变量i , 而*f* 函数处理的是全局变量i ,*loadstring* 总是在全局环境中进行编译。 *loadstring* 最典型的用途是:运行外来代码,例如网络上的,别人的。。。注意,*loadsting* 只能load语句,不能load表达式, 如果你想算一个表达式的值,那么前面要加上一个*return* 来返回给定表达式的值。下面是一个示例帮助理解: ~~~ print "enter your expression:" local l = io.read() local func = assert(loadstring("return " .. l)) print("the value of your expression is " .. func()) ~~~ 看一下运行情况:(注意第一个为什么报错了,想想什么才叫表达式) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef02e398.PNG) *loadstring* 返回的就是一个普通的函数,可以多次调用: ~~~ print "enter function to be plotted (with variable 'x'):" local l = io.read() local f = assert(loadstring("return " .. l)) for i=1,20 do x = i -- global 'x' (to be visible from the chunk) print(string.rep("*", f())) end ~~~ (*string.rep* 函数复制一个string给定的次数),下面是运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0464c6.PNG) 如果我们在深究一下,其实不管*loadstring* 也好,*loadfile* 也好,Lua中最基础的函数是*load* 。*loadfile* 从一个file中加载代码块,*loadstring* 从一个string中加载代码块,而*load* 调用一个*reader* 函数来获取代码块,这个*reader* 函数分块返回代码块,*load* 调用它,直到它返回**nil**。我们很少使用*load* 函数;通常只有在代码块不是位于一个file中,但是又太大了,不适合放到内存中(如果适合放到内存中,那就可以用*loadstring* 了)的时候,才会用load 。 Lua将每一个独立的代码块看作是一个含有不定数量参数的匿名函数。例如,*loadstring*("a = 1")跟下面的表达式基本等价: ~~~ function (...) a = 1 end ~~~ 跟其他函数一样,代码块也可以声明局部变量: ~~~ f = loadstring("local a = 10; print(a + 20)") f() --> 30 ~~~ 利用这个特性,我们可以重写上面的一个例子: ~~~ print "enter function to be plotted (with variable 'x'):" local l = io.read() local f = assert(loadstring("local x = ...; return " .. l)) for i=1,20 do print(string.rep("*", f(i))) end ~~~ 在代码的开始处,加了“local x = ...”,将x声明为局部变量。调用*f*  函数的时候,实参i 就变成了变参表达式"..."的值。运行结果,跟上面一样,就不截图了。 *load *函数不会发生运行时错误崩溃。如果出错了,总是返回**nil**+err_msg: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef05f4e3.PNG) 一个很常见的误解:将load代码块与定义函数划等号。在Lua中,函数定义实际上是赋值行为,而且是在运行时才发生,而不是在编译时。例如,我们有个文件foo.lua: ~~~ function foo (x) print(x) end ~~~ 接着运行下面这条cmd: ~~~ f = loadfile("foo.lua") ~~~ 这个时候,foo被编译了,但是还没有被定义。要定义它,必须运行这个代码块: ~~~ print(foo) --> nil f() -- defines 'foo' foo("ok") --> ok ~~~ 在生产环境中的程序,在运行外部代码的时候,要尽可能的捕获所有的错误并作出处理。甚至,如果这些代码不被信任,那就应该在一个安全的环境中运行,避免在运行这些外来代码的时候,产生一些不愉快的事情。 ## 2. C代码 不像Lua代码,C代码必须先跟程序链接才能被使用。在大多数系统中,做这个链接动作的做简单的方法是:使用动态链接功能。那么怎么检测你的环境是否已经支持这个功能呢? 运行 *print(package.loadlib("a", "b"))*。如果出现类似说文件不存在这样的错误,那么就说明你的环境支持这个动态链接功能啦。否则,出现的错误信息会告诉你这个功能不支持,或者没有被安装。下面是我的环境的表现: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef077024.PNG) *package.loadlib* 有两个string类型的参数,第一个是库的完整路径,第二个是函数的名字。因此,一个典型的调用应该跟下面很相似: ~~~ local path = "/usr/local/lib/lua/5.1/socket.so" local f = package.loadlib(path, "luaopen_socket") ~~~ *loadlib* 函数,加载一个给定的库,并链接,但是并没有去调用那个函数。而是将这个C 函数当作Lua函数返回。如果这个过程出现什么错误,那么*loadlib* 会返回**nil**+ err_msg。 要使用*loadlib* 函数,我们必须要给出库的完整路径和准确的函数名字,好像有点麻烦。这里有个替代方案,*require* 函数。我们后面在讨论这个函数,这里只需知道有这么个东西就可以了。 ## 3. 错误 是个人就难免会犯错。因此我们要尽可能的处理可捕获的错误。因为Lua是一个扩展语言,通常是被嵌入到别的程序(姑且叫做宿主程序吧)中,在出错的时候,不能简单的让它崩溃掉或者退出。而是结束执行当前的代码块,并返回到宿主程序中。 Lua遇到任何非期望的条件,都会产生一个错误。例如,对非数值进行加运算,调用一个非函数的值,索引一个非table的值,等等。可以显式地调用error 函数来产生一个错误。例如: ~~~ print "enter a number:" n = io.read("*number") if not n then error("invalid input") end ~~~ *if not condition then error end*,在Lua中被封装成了assert 函数: ~~~ print "enter a number:" n = assert(io.read("*number"), "invalid input") ~~~ *assert* 检查它的第一个参数,如果为**nil**或者**false**,就产生一个error。第二个参数是可选的。 在函数发现错误时,有两种处理方式,一个是返回error code,另一个是产生错误(联想下C语言中的*assert*)。如何选择呢?建议如下:可以轻松避免的错误,可以通过编码来修改并规避的,产生错误;否则返回errcode。 举个例子,sin 函数,如果参数用了一个table,假设它返回了一个error code,如果我们需要去检查一下这个错误,那么代码应该像下面这样写: ~~~ local res = math.sin(x) if not res then -- error? ~~~ 但是,实际上,我们可以在调用sin 函数之前就检查一下x 是否合法: ~~~ if not tonumber(x) then -- x is not a number? ~~~ 如果参数x不是一个数值,那么意味着你的程序中某个地方出错了。这种情况下,停止运行并产生一个错误信息,是最简单有效的方式来处理这个错误。 我们再来看下*io.open* 函数,当我去open一个并不存在的file时,会怎么样呢?这个情况,并不能提前检查这个file是否存在,因为在很多系统中,要想知道某个file是否存在,只有去尝试打开它。因此,如果函数*io.open* 因为一些外部原因(例如file does not exist, permisson denied)而不能打开一个file,它会返回**nil**+ err_msg。这样的话,我们就可以进行一些处理,例如要求用户重新输入一个文件名: ~~~ local file, msg repeat print "enter a file name:" local name = io.read() if not name then return end -- no input file, msg = io.open(name, "r") if not file then print(msg) end until file ~~~ 如果仅仅希望保证*io.open* 能够正常工作,可以简单的使用: ~~~ file = assert(io.open(name, "r")) ~~~ 这是Lua中的一个习惯用法,如果*io.open*失败了,就会产生一个错误。 ## 4. 错误处理和异常 对大多数程序来说,不需要在Lua代码中进行错误处理,宿主程序本身会对错误进行相应处理。所有的Lua动作基本都是由宿主程序调用起来的,如果发生错误,Lua代码块只需要返回相应的err_code,宿主程序本身针对err_code做出相应的处理。在独立的Lua解析器中,出错的时候,也只是打印出相应的错误信息,然后继续提示用户继续进行运行其他的命令。 如果想要在Lua中对错误进行处理,那么必须用*pcall* (protected call)函数来封装代码。 假设我们运行一段lua代码,并捕获到运行过程中出现的错误。我们首先要做的就是封装这段代码,假设封装成函数*foo* : ~~~ function foo () if unexpected_condition then error() end print(a[i]) -- potential error: 'a' may not be a table end ~~~ 然后用*pcall* 去调用这个函数: ~~~ if pcall(foo) then -- no errors while running 'foo' else -- 'foo' raised an error: take appropriate actions end ~~~ 上面的*foo* 函数也可以替换成匿名函数的。 *pcall* 会在protected mode下调用它的第一个参数,以便能够在函数运行过程中捕获到出现的错误。如果函数运行正常,没有错误产生,*pcall* 返回**true**+ 函数的返回值;如果出现错误,*pcall* 返回**false**+ err_msg。 err_msg不一定非得是string,任何传递给error 的值都会被pcall 返回,例如下面的示例: ~~~ local status, err = pcall(function () error({code=121}) end) print(status, err.code, type(err)) ~~~ 运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef091498.PNG) ## 5. 错误信息和堆栈 像上面说的,任何类型的值都可以作为err_msg,但是,通常err_msg还是string类型的,说明发生了什么错误。当遇到了内部错误(例如试图索引一个非table值),Lua负责产生err_msg;否则,err_msg就是传递给error 函数的值。另外,Lua总是在错误发生的地方添加一些位置信息, 如下示例: ~~~ local status, err = pcall(function () a = "a"+1 end) print(err) ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0acc62.PNG) ~~~ local status, err = pcall(function () error("my error") end) print(err) ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0c46ab.PNG) 位置信息指明了filename和line number。 *error* 函数有一个额外的参数level, 说明如何获取错误发生的位置。level默认为1,返回error 函数被调用的位置; level 为2, 返回调用*error* 函数的函数被调用的位置; level 为0,不获取位置信息。对比下下面三段代码的的区别和执行结果就明白了。 ~~~ function foo(str) if type(str) ~= "string" then error("string expected", 0) --level 0 end print("foo success") end local status, err = pcall(foo(3)) print(err) ~~~ 运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0d8604.PNG) ~~~ function foo(str) if type(str) ~= "string" then error("string expected", 1) --level 1 end print("foo success") end local status, err = pcall(foo(3)) print(err) ~~~ 运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0f1a7c.PNG) ~~~ function foo(str) if type(str) ~= "string" then error("string expected", 2) --level 2 end print("foo success") end local status, err = pcall(foo(3)) print(err) ~~~ 运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef114199.PNG) err_test03.lua, err_test04.lua是我code文件的名字而已,可能每个人起名字都不同的。注意一下level 1 和level 2的错误行号是不同的。通过这3个例子,很明白了吧。 通常情况下,在程序出错的时候,可能仅仅知道错误发生的位置是不够的。至少,还需要函数的调用堆栈吧。但是当*pcall* 函数返回的时候,堆栈信息已被部分破坏了。因此,为了获取堆栈信息,必须在*pcall* 返回之前就建立它。Lua为我们提供了*xpcall* 函数, 它比*pcall* 函数多一个参数*error handler function*。 一旦发生错误,Lua在堆栈被损坏之前调用这个*handler* 函数,在*handler* 中,我们可以用*debug* 库来收集尽可能的有关错误的信息。两个常用的*handler* 函数是*debug.debug* 和*debug.traceback* ;具体用法,后续会详细讨论。 终于写完这篇了,关电脑睡觉去。 水平有限,如果有朋友发现错误,欢迎留言交流
    ';

    generic for

    最后更新于:2022-04-01 20:44:13

    下面写一下怎么给generic***for ***写迭代器。 ## 1. 迭代器和闭包 在Lua中,迭代器用function表示,每次调用该function,都会返回集合中的next元素。 每个迭代器都要在连续的调用之间保存一些state,这样才能知道进行到哪一步,下一步该从哪开始处理。在Lua中,闭包可以处理这个问题。闭包结构包含两个function:一个是闭包本身,另一个是factory,用来创建闭包。下面是个简单的示例: ~~~ function values(t) local i = 0 return function () i = i + 1; return t[i] end end ~~~ 在上面的例子中,values是factory。每次调用factory,都会创建一个新的闭包(迭代器本身)。该闭包在变量t和i中保存state。每次调用这个迭代器,都会从t中返回next元素的值。返回最后一个元素后,它会返回nil,标志着迭代器结束。 可以将上面的迭代器用在***while ***中,但是用generic***for ***更简单: ~~~ t = {10, 20, 30} function value(t) local i = 0 return function () i = i + 1; return t[i] end end iter = value(t) while true do local element = iter() if element == nil then break end print(element) end t = {1, 2, 3} for element in value(t) do print(element) end ~~~ 执行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eefa9aeb.PNG) generic***for ***为迭代器循环过程在内部保存state,不需要iter变量;每次迭代都调用该迭代器,并在迭代器返回nil时停止循环。 下面是一个复杂点的例子,从当前输入的文件中遍历所有的word。遍历过程中保存两个值:当前行(变量line),当前位置(pos),***string.find ***函数从当前行中搜索一个word,用'%w+'匹配,在匹配到word后,将当前位置pos置于该word之后的第一个字符处,并返回该word。否则,迭代器读入一个新行再重复上面的过程。如果没有更多的行,返回nil,标志迭代结束。 ~~~ function allwords () local line = io.read() local pos = 1 print("allwords begin") return function () while line do local s, e = string.find(line, "%w+", pos) if s then pos = e + 1 print("return the word") return string.sub(line, s, e) else print("read next line") line = io.read() pos = 1 end end print("return nil, iter end") return nil end end for word in allwords() do print(word) end ~~~ 执行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eefbe309.PNG) 注意执行打印出来的一些信息,可以帮助了解脚本的执行过程。 ## 2. Generic***for ***的语义 前面的例子有一个缺陷,每次都需要创建一个新的闭包来初始化一个新的循环。大多数情况下,这不是什么太大的开销。但是,有时也是不能接受的。这时就需要用generic***for ***本身来保存迭代器状态。 Generic***for ***会保存3个值:iterator function, invariant state,control variable。generic***for ***的语法如下: ~~~ for in do end ~~~ 这里,var-list是一个或多个变量的名字,用逗号分隔,exp-list是一个或多个表达式,也用逗号分隔。通常exp-list只有一个元素,对iterator factory的调用。例如: ~~~ for k, v in pairs(t) do print(k, v) end ~~~ var-list是k,v, exp-list是pairs(t)。通常,var-list也只有一个变量,如下: ~~~ for line in io.lines() do io.write(line, "\n") end ~~~ var-list的第一个变量为control variable,它的值为nil时表示循环结束。 ***for ***循环先计算in后面的表达式的值,这些表达式会产生3个值,就是上面写的generic***for* **需要保存的3个值:iterator function, invariant state,control variable 的初始值。跟多赋值语句一样,只有最后一个表达式可以产生多个值;但是所有的表达式的结果最终被调整为3个,多的丢弃,少的补nil (当使用简单的迭代器的时候,factory只返回iterator function,invariant state和control variable的值为nil)。 完成上面的初始化步骤后,***for***用invariant state和control variable作为参数来调用iterator function。然后***for***将iterator function返回的值赋值给var-list中的变量,如果返回的第一个值为nil,那么循环结束。否则,***for***执行循环体代码并再次调用iteration function,重复上面的过程。 下面的伪代码也许能帮助理解: ~~~ for var_1, ..., var_n in do end ~~~ 等价于: ~~~ do local _f, _s, _var = while true do local var_1, ... , var_n = _f(_s, _var) _var = var_1 if _var == nil then break end end end ~~~ 如果iterator function为*f* ,invariant state为*s* , control variable的初始值为*a0*,control variable会如下循环: *a1* =*f* (*s *,*a0 *),*a2* =**f (*s *,*a1 *), *a3* =*f* (*s *,*a2 *), .... 直到ai为nil。 ## 3. 无状态迭代器 顾名思义,无状态迭代器自身不会保存任何state。因此,我们可以在多次循环里面用同一个迭代器,避免了创建新闭包的开销。 每次迭代,for 循环用两个参数 invariant state 和 control variable 来调用 iterator 函数。ipairs 是个典型的此类迭代器: ~~~ a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end ~~~ 迭代器的state 是被遍历的table ,当前的index 是 control variable 。ipairs 和 iterator 实现都比较简单,下面是一个示例: ~~~ local 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 ~~~ 在for 循环中,Lua调用 ipairs(a) ,得到3个值: iter 函数为iterator , a 为invariant state , 0 是control variable 的初始值。 然后,Lua调用 iter(a,0), 得到1, a[1] 。下一次迭代,调用 iter(a,1), 得到 2, a[2],直到第一个nil值,迭代结束。 pairs 函数跟上面的 ipairs 类似,只是iterator 函数为 next, 不是iter。next 是Lua中的一个基本函数: ~~~ function pairs (t) return next, t, nil end ~~~ next(t, k) 调用,k 是 table t 中的一个key ,返回两个值,next key 和 t[next key],next(t, nil)返回 table 中的第一对值。例如: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eefd7e58.PNG) 很多人比较喜欢直接用next ,而不是用pairs: ~~~ for k, v in next, t do end ~~~ for 循环的exp-list会被调整为3个值,Lua会得到next,t,nil,跟调用pairs(t)得到的结果是一样的。 下面是一个遍历链表的例子,代码有点绕,仔细想一下就会明白的。 ~~~ local function getnext (list, node) return not node and list or node.next end function traverse (list) return getnext, list, nil end list = nil for line in io.lines() do list = {val = line, next = list} end for node in traverse(list) do print(node.val) end ~~~ ## 4. 复合状态的迭代器 很多时候,需要保存的state,除了 invariant state 和 control variable 还有别的。这种情况,用闭包是最简单的方案。还有一个可选方案,用table,把所有需要保存的东西都打包到table 中,想保存什么就把什么打包到table 中。在循环过程中,尽管state 始终是同一个table,但是table中的数据可以在循环过程中更改。迭代器把所有需要的数据到封装到table 中,所有就忽略了generic *for* 提供的第二个参数。 下面把allwords那个示例改写了一下,来展示一下上面所说的用法 ~~~ local iterator function allwords() local state = {line = io.read(), pos = 1} return iterator, state end function iterator(state) while state.line do --search for next word local s, e = string.find(state.line, "%w+", state.pos) if s then -- update next position (after this word) 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 word in allwords() do print(word) end ~~~ 运行结果示例如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef000aa3.PNG) 我们应该尽可能的使用无状态的迭代器,将所有的state都保存到*for* 循环的变量中。在开始新的循环的时候就不需要创建新的对象。当你的迭代器不适合上面说的table的情况时,应该用闭包。用闭包更简洁优美,并且效率更高,因为:1. 创建闭包比创建table开销要小。 2. 访问非局部变量(闭包中)比访问table的域要更快。 后面还会讲讲用*coroutines* 来实现迭代器,更强大,但是代价也更高。 ## 5. 真正的迭代器 “迭代器”这个名字其实有点误导,因为我们的“迭代器”根本不做迭代操作,真正去做迭代操作的是 for 循环。“迭代器”只是为迭代过程提供连续的值。可能更好的名字应该叫“生成器”。 不过,下面展示一种真正去做迭代操作的“迭代器”,再把allwords 重写一次: ~~~ function allwords(f) for line in io.lines() do for word in string.gmatch(line, "%w+") do f(word) -- call the function end end end allwords(print) print("=============================") local count = 0 allwords(function(w) if w == "hello" then count = count + 1 end end) print("the number of 'hello' is " .. count) ~~~ 示例中 的参数f 可以传相关的函数,示例中简单的用了*print* 来显示了一下句子中的word,用了一个匿名函数统计句子中的“hello”个数。 执行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5ef0185b0.PNG) 这种形式的迭代器在旧版本的Lua中很流行,那是Lua没有*for* 语句。跟“生成器”形式的迭代器比起来,到底哪一种好呢?两种开销差不多,每次迭代都是一次函数调用。从一方面讲,这种旧式迭代器,更容易写。但是“生成器”形式的迭代器更灵活。首先,她可以同时进行两个平行的迭代(例如,比较两个句子中的word,一个一个的比),另外,可以用*break* 在迭代过程中退出。总的来说,“生成器”形式的迭代器也许更受欢迎一点。 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    函数(二)

    最后更新于:2022-04-01 20:44:11

    在Lua中,你可以像使用number和string一样使用function。可以将function存储到变量中,存储到table中,可以当作函数参数传递,可以作为函数的返回值。 在Lua中,function跟其他值一样,也是匿名的。function被作为一个值存储在变量中,下面这个例子有点2,可以帮助理解: ~~~ a = {p = print} a.p("Hello World") --> Hello World print = math.sin -- 'print' now refers to the sin function a.p(print(1)) --> 0.841470 sin = a.p -- 'sin' now refers to the print function sin(10, 20) --> 10 20 ~~~ 创建函数的表达式 ~~~ function foo (x) return 2*x end --实际上为: foo = function (x) return 2*x end ~~~ 从上面可以看出,function的定义实际上是创建一个'function'类型的值,并赋值给一个变量。我们可以将*function (x) body end *看成是function的构造函数,就像{}是table的构造函数一样。 table库有一个函数*table.sort *,接受一个table,然后对table中的元素排序。排序可能有各种规则,升序,降序,数字或字母表顺序,根据table中的key排序等。该函数没有提供各种各样所有的排序选项,而是提供了一个单独的选项,即函数的第二个参数,*order*函数,*order*函数,接受两个元素,返回一个布尔值,来指示是否第一个元素排在第二个元素之前。看下面的例子: ~~~ network = { {name = "grauna", IP = "210.26.30.34"}, {name = "arraial", IP = "210.26.30.23"}, {name = "lua", IP = "210.26.23.12"}, {name = "derain", IP = "210.26.23.20"}, } table.sort(network, function (a,b) return (a.name > b.name) end) ~~~ 从上面看,匿名函数使用起来很方便吧。 这里先讲一个概念,higher-order function。它可以把其他函数当作参数。下面给个示例,求导函数(哎,好多年前的东西,一点都不记得了,还跟同事讨论了半天) ~~~ function derivative (f, delta) delta = delta or 1e-4 return function (x) return (f(x + delta) - f(x))/delta end end c = derivative(math.sin) print(math.cos(10), c(10)) --> -0.83907152907645 -0.83904432662041 ~~~ 文章开头也说了,Lua中的function可以像普通的值一样使用,可以存储到全局变量,局部变量,table中。往下看,function存储到table中,这是个牛B的特性,可以实现很多高级的功能,例如模块,面向对象等。 ## 1. 闭合函数 先看一个示例,有两个table,一个table中是student names,另一个table里面有每个student的grade;现在要根据student的grade来对前一个table进行排序。根据之前所学,可以使用如下code: ~~~ names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Paul = 7, Peter = 8} table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) ~~~ 现在写一个function来实现这一个功能: ~~~ function sortbygrade (names, grades) table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) end ~~~ 上面这段code的有趣之处在于,sort中的匿名函数可以访问参数中的grades,grades是sortbygrade的局部变量。在该匿名函数中,grades既不是局部变量,也不是全局变量,而是非局部变量(智商有点捉鸡了)。 现在来看看这个非局部变量的妙用。看下面示例: ~~~ function newCounter () local i = 0 return function () -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2 c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2 ~~~ 在上面的代码中,匿名函数引用了非局部变量*i *,来记数。但是,调用这个匿名函数的时候,i已经不再在有效作用域了,因为创建*i *的函数(*newCounter *)已经返回了。但是Lua可以使用闭合函数正确处理这种情况。简单地说,闭合函数就是一个函数加上要访问一个非局部变量所需要的所有元素。如果再调用一个*newCounter *,它会重新创建一个新的非局部变量i, 得到一个新的闭合函数。如上面示例,c1和c2是两个基于用一个函数的闭合函数,二者有两个独立的非局部变量*i *的实例。 闭合函数在很多场合都很好用。例如,在higer-order函数(如*sort *)中,可以作为参数;在newCounter这样的在自己函数体中创建其他的函数;作为回调函数。一个典型的例子就是,在传统的GUI工具箱中,创建按钮,每个按钮需要一个回调函数来响应按键动作。例如一个计算器,需要10个类似的按钮,每个数字一个。可以用下面的function来创建: ~~~ function digitButton (digit) return Button{ label = tostring(digit), action = function () add_to_display(digit) end } end ~~~ 在上面的示例中,我们假设digitButton是一个工具箱函数,用来创建一个按钮,lable是按钮的label, action是回调函数,响应按键操作。这个回调函数可以在digitButton完成工作以后很久,局部变量digit超出作用域后再调用,但是它仍然能够访问变量digit。 由于函数是存储在普通的变量中,Lua可以方便的重新定义一些函数,甚至Lua预定义的函数。下面看一个示例,重新定义sin函数,将参数由原来的弧度,改成度数。新的函数一定要转换一下参数的值,然后调用原来的sin函数来实现功能。 ~~~ oldSin = math.sin math.sin = function (x) return oldSin(x*math.pi/180) end --建议用下面这种 do local oldSin = math.sin local k = math.pi/180 math.sin = function (x) return oldSin(x*k) end end ~~~ 推荐使用第二种实现,我们将原来的函数保存在一个局部变量中中,访问它的唯一途径就是通过新版本的函数。通过这种技巧,可以构建沙箱安全环境。这种安全环境在你需要运行一些不被信任的代码(例如从internet上收到的代码)时是很有必要的。例如,为了严格限制程序可以访问的文件,可以重新定义函数io.open: ~~~ do local oldOpen = io.open local access_OK = function (filename, mode) --here to add some code to restrict access end io.open = function (filename, mode) if access_OK(filename, mode) then return oldOpen(filename, mode) else return nil, "access denied" end end end ~~~ 如下代码,重定义open函数后,程序没办法访问无限制版本的open函数,只能使用这个限制版本。通过这种方法,Lua可以简便灵活地构建安全的沙箱环境。 ## 2.非全局函数 Lua中的函数可以存储到全局变量中,也可以存储到table和局部变量中。 大多数的Lua库都是将函数存入table中。下面示例集中定义table中函数的方法: ~~~ Lib = {} Lib.foo = function (x,y) return x + y end Lib.goo = function (x,y) return x - y end --使用构造函数 Lib = { foo = function (x,y) return x + y end, goo = function (x,y) return x - y end } --另一种语法 Lib = {} function Lib.foo (x,y) return x + y end function Lib.goo (x,y) return x - y end ~~~ 当我们将函数存储到一个局部变量中,我们就得到一个局部函数,只在给定的作用域中有效。这个特性在包中经常用到,可以在一个包中定义局部函数,这些函数只在该包中可见,包中的其他函数可以调用这些局部函数: ~~~ local f = function () end local g = function () f() -- 'f' is visible here end ~~~ 在**递归**函数中有一点微妙,看下面两份代码。 ~~~ local fact = function (n) if n == 0 then return 1 else return n*fact(n-1) -- buggy end end --下面这个可以工作 local fact fact = function (n) if n == 0 then return 1 else return n*fact(n-1) end end ~~~ 再看下面这个局部函数定义展开后的形式: ~~~ local function foo () end --expands to local foo foo = function () end ~~~ 因此,上面的递归函数也可以写成,注意3中实现方式的不同。 ~~~ local function fact (n) if n == 0 then return 1 else return n*fact(n-1) end end ~~~ 但是,上面这个技巧在非直接递归函数中就不好使了啊。看下面的示例: ~~~ local f, g -- 'forward' declarations function g () f() end --local f 如果这句放在这,那么上面的g()是引用不到正确的f函数的 function f () g() end --调用一下,要把上面的代码补全哦 g() ~~~ 不信,试试把local f这句放到fucntion g ()的定义后面看有什么后果。 ## 3.强大的尾调用 程序员都知道,函数的调用会产生调用堆栈。但是在Lua中,当然也有调用堆栈啦。但是尾调用在Lua中就不同于其他编程语言啦。我们先通过几行代码来看下什么是尾调用tailor call ~~~ function f (x) return g(x) end function foo (n) if n > 0 then return foo(n - 1) end end --下面这几个都不是 function f (x) g(x) --after calling g, f still has to discard occasional results from g before returning end function f (x) return g(x) + 1 -- must do the addition end function f (x) return x or g(x) -- must adjust to 1 result end function f (x) return (g(x)) -- must adjust to 1 result end ~~~ 在Lua中,只用格式为return func(args)的调用才是尾调用。即使func和args都是复杂的表达式也没关系,因为Lua在调用之前算得到它们的值。所以下面这个也是尾调用 ~~~ return x[i].foo(x[j] + a*b, i + j) ~~~ 我们再来针对下面这行代码讲讲Lua中对尾调用处理的强大。 ~~~ function f (x) return g(x) end ~~~ Lua是怎么处理尾调用的呢。像C语言的话,上面代码中,f对g调用后,g执行完毕后,会返回到g的被调用处。但是在Lua中,这是一个尾调用,g执行完毕之后,会直接返回到f的被调用处。这样的话,可以节省很多堆栈空间。因此像下面这个函数,就不需担心n太大的话会有溢出。 在Lua中,对尾调用的一个很好的应用是状态机。可以用一个函数表示一个状态,改变状态就是跳转到一个指定的函数。下面我们用一个简单的迷宫程序示例:迷宫有几个房间(我们这里是4个),每个房间有4扇门,通向东,南,西,北。每一步,玩家指定一个移动的方向,如果这个方向有门,那么就进入对于的房间;否则,程序给一个警告;目标是从开始的房间走到目标房间。 这个程序是一个典型的状态机,状态就是当前的房间,每个房间写一个函数。用尾调用来从一个房间移动到另一个房间。如果不用尾调用的话,每一次移动都要将堆栈升级一个level,一定数量的移动后,可能就会导致程序溢出了。使用尾调用的话,就不需要担心这个问题了。废话少说,下面是代码,已经验证过了,比较简单。 ~~~ function room1 () local move = io.read() if move == "south" then return room3() elseif move == "east" then return room2() else print("invalid move") return room1() -- stay in the same room end end function room2 () local move = io.read() if move == "south" then return room4() elseif move == "west" then return room1() else print("invalid move") return room2() end end function room3 () local move = io.read() if move == "north" then return room1() elseif move == "east" then return room4() else print("invalid move") return room3() end end function room4 () print("congratulations!") end --写完上面的四个room,调用一下就可以了。 room1() ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    函数(一)

    最后更新于:2022-04-01 20:44:09

    在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。下面有3个例子,分别将函数当作一条语句;当作表达式(后面两个是一类)。 ~~~ print(8*9, 9/8) --> 72 1.125 a = math.sin(3) + math.cos(10) --> a = -0.69795152101659 print(os.date()) --> Sat Mar 9 12:14:08 2013 ~~~ 函数如果带参数,那么就要用(arg1, arg2,...)括起来,如果没有参数,就写个空(),说明这是个函数调用。 特例,如果函数只有一个参数,并且参数类型是字符串或者table,那么()可以省略,如下示例: ~~~ print "Hello World" <==> print("Hello World") dofile 'a.lua' <==> dofile('a.lua') print[[a multi-line <==> print([[a multi-line message]] message]]) f{x=10, y=20} <==> f({x=10, y=20}) type{} <==> type({}) ~~~ Lua支持面向对象,操作符为冒号‘:’。o:foo(x) <==> o.foo(o, x),这个在后面会专门写一篇文章。 Lua程序可以调用C语言或者Lua实现的函数。Lua基础库中的所有函数都是用C实现的。调用一个用C实现的函数,和调用一个用Lua实现的函数,二者没有任何区别。 Lua函数的定义语法比较常规,如下示例: ~~~ function add(a) local sum = 0 for i, v in ipairs(a) do sum = sum + v end return sum end ~~~ 函数的参数跟局部变量一样,用传入的实参来初始化,多余的实参被丢弃,多余的形参初始化为nil。示例如下: ~~~ function f(a, b) return a or b end f(3) -- a=3, b=nil f(3, 4) -- a=3, b=4 f(3, 4, 5) -- a=3, b=4 (5被丢弃) ~~~ 虽然Lua可以处理这样的情况,但是不鼓励这种传入错误数量参数的函数调用,可能会使程序运行时有点小问题。不过,有些情况下,这个特性可以加以利用,例如下面示例的默认参数: ## 1.多返回值 不同于常规函数,Lua的函数可以返回多个返回值。一些Lua中预定义的函数可以返回多个返回值。例如*string.find*函数,在string中匹配一个sub-string,*string.find*返回sub-string的起始位置和结束位置。利用多赋值语句来获取函数的多个返回值。 用Lua写的函数也可以返回多个返回值,如下示例,查找array中的最大值,并返回其位置和值 Lua会根据实际情况来使函数的返回值个数适应调用处的期望。 1)如果一个函数调用作为一条语句,所有的返回值都被丢弃 2)如果一个函数调用作为一个表达式,除了3)的情况,返回值只保留第一个 3)在多赋值,返回值作为实参来调用其他函数,table中,return语句中,这4种调用场景,如果函数调用作为其最后一个表达式,那么会保留所有的返回值,然后根据实际调用需要再纠正。 示例: ~~~ -- 多赋值,函数调用是最后一个表达式 x,y = foo2() -- x="a", y="b" x = foo2() -- x="a", "b" is discarded x,y,z = 10,foo2() -- x=10, y="a", z="b" x,y = foo0() -- x=nil, y=nil x,y = foo1() -- x="a", y=nil x,y,z = foo2() -- x="a", y="b", z=nil -- 多赋值,函数调用不是最后一个表达式,因此返回值只保留第一个 x,y = foo2(), 20 -- x="a", y=20 x,y = foo0(), 20, 30 -- x=nil, y=20, 30 is discarded -- 返回值作为实参来调用其他函数 print(foo0()) --> print(foo1()) --> a print(foo2()) --> a b print(foo2(), 1) --> a 1 print(1, foo2()) --> 1 a b print(foo2() .. "x") --> ax (see next) -- table中 t = {foo0()} -- t = {} (an empty table) t = {foo1()} -- t = {"a"} t = {foo2()} -- t = {"a", "b"} -- table中,但是函数调用不是最后一个表达式 t = {foo0(), foo2(), 4} -- t[1] = nil, t[2] = "a", t[3] = 4 -- return语句中 function foo (i) if i == 0 then return foo0() elseif i == 1 then return foo1() elseif i == 2 then return foo2() end end print(foo(1)) --> a print(foo(2)) --> a b print(foo(0)) -- (no results) print(foo(3)) -- (no results) ~~~ 用括号来强制返回值个数为一个: ~~~ print((foo0())) --> nil print((foo1())) --> a print((foo2())) --> a ~~~ 因此,括号千万别乱用,尤其是return后的值,如果用了括号,那么就只返回一个值。 函数unpack可以返回多个值,它传入一个array,然后返回array中的每一个值。 ~~~ print(unpack{10,20,30}) --> 10 20 30 a,b = unpack{10,20,30} -- a=10, b=20, 30 is discarded ~~~ unpack的一个重要用法是泛型调用,提供了比C语言中更大的灵活性。在Lua中,如果你想调用一个函数*f,*传入可变数量的参数,很简单, ~~~ f(unpack(a)) ~~~ unpack返回a中的所有值,并传给*f *作为参数,下面示例: ~~~ f = string.find a = {"hello", "ll"} print(f(unpack(a))) --> 3 4 ~~~ Lua中的unpack是用C实现的。其实我们也可以用Lua来实现它 ~~~ function unpack (t, i) i = i or 1 if t[i] then return t[i], unpack(t, i + 1) end end ~~~ ## 2. 变参 Lua中的一些函数接受可变数量的参数,例如print函数。print函数是用C来实现的,但是我们也可以用Lua来实现变参函数。下面是一个示例: ~~~ function add (...) local s = 0 for i, v in ipairs{...} do s = s + v end return s end print(add(3, 4, 10, 25, 12)) --> 54 ~~~ 参数列表中的'...'指明该函数可以接受变参。我们可以将‘...’当作一个表达式,或者一个多返回值的函数(返回当前函数的所有参数)。例如 ~~~ local a, b = ... ~~~ 用可选参数的前两个初始化局部变量a,b的值。再看下面的例子, ~~~ function foo(a, b, c) <==> function foo(...) local a, b, c = ... ~~~ ~~~ function id (...) return ... end ~~~ 上面这个函数简单地返回它所有的参数。下面的例子,说明了一个跟踪函数调用的技巧 ~~~ function foo1 (...) print("calling foo:", ...) return foo(...) end ~~~ 再看一个实用的例子。Lua提供了不同的函数来格式化文本*string.formant*和写文本*io.write*,我们可以简单地将二者合二为一。 ~~~ function fwrite (fmt, ...) return io.write(string.format(fmt, ...)) end ~~~ 注意有一个固定的参数fmt。变参函数可能含有不定数目的固定参数,后面再跟变参。Lua会将前面的实参赋值给这些固定参数,剩下的实参才能当作变参看待。下面是几个示例: ~~~ CALL PARAMETERS fwrite() -- fmt = nil, no varargs fwrite("a") -- fmt = "a", no varargs fwrite("%d%d", 4, 5) -- fmt = "%d%d", varargs = 4 and 5 ~~~ 如果想要迭代处理变参,可以用{...}来将所有的变参收集到一个table中。但是有时变参中可能含有非法的*nil*,我们可以用*select*函数。*select*函数有一个固定的参数*selector*,然后跟一系列的变参。调用的时候,如果*selector*的值为数字n,那么select函数返回变参中的第n个参数,否则*selector*的值为'#',*select*函数会返回可变参数的总数目。下面示例: ~~~ for i=1, select('#', ...) do local arg = select(i, ...) -- get i-th parameter end ~~~ 注意,select("#", ...)返回变参的数目,包括nil在内。 ## 3. 带名字的参数 Lua中函数的参数传递是基于位置的,当调用函数的时候,实参根据位置来匹配形参。但是,有的时候,根据名字来匹配更实用。例如,系统函数*os.rename*,我们会经常忘记新名字和旧名字哪个在前;为了解决这个问题,我们尝试重新定义这个函数。下面这个 ~~~ -- invalid code rename(old="temp.lua", new="temp1.lua") ~~~ 上面这个代码是非法的,Lua并不支持这样的语法。但是我们可以修改一点点,来实现相同的效果。 ~~~ function rename (arg) return os.rename(arg.old, arg.new) end ~~~ 用这种方式来传递参数是很实用的,尤其是,当函数有多个参数,并且其中一些是可有可无时。例如,用GUI库创建一个新的窗口 ~~~ w = Window{ x=0, y=0, width=300, height=200, title = "Lua", background="blue", border = true } ~~~ *Window*函数可以检查必须的参数,并且给可选参数赋予默认值等。假设*_Window*函数可以用来创建一个新窗口,但是它必须要全部的参数。那我们就可以重新定义一个*Window*函数如下: ~~~ function Window (options) -- check mandatory options if type(options.title) ~= "string" then error("no title") elseif type(options.width) ~= "number" then error("no width") elseif type(options.height) ~= "number" then error("no height") end -- everything else is optional _Window(options.title, options.x or 0, -- default value options.y or 0, -- default value options.width, options.height, options.background or "white", -- default options.border -- default is false (nil) ) end ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    语句

    最后更新于:2022-04-01 20:44:06

    Lua支持大多数传统的语句,跟C语言和Pascal差不多。传统的语句包括:赋值,控制结构,流程调用等。Lua还支持一些不太传统的语句,例如多赋值(听起来有点怪,往下看就明白了)和局部变量声明(这个好像也是传统的吧)。 ## 1. 赋值 赋值是改变一个变量的值或者table的域的最基本的方法: ~~~ a = "hello" .. "world" t.n = t.n + 1 ~~~ Lua支持多赋值,多个值对应于多个变量,值和变量都分别用逗号','隔开。 ~~~ a, b = 10, 20 ~~~ 在上面的语句中,得到的结果为a=10, b=20。 在多赋值语句中,Lua首先计算出所有的值,然后才会做赋值操作。因此,我们可以利用多赋值语句交换两个变量的值,如下: ~~~ x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]' ~~~ Lua总是根据变量的数目来纠正值的数目:当变量的数目较多时,多出来的变量赋值为nil,相反,如果值的数目过多,那么多于的值会被丢弃: ~~~ a, b, c = 0, 1 print(a, b, c) --> 0 1 nil a, b = a+1, b+1, b+2 -- value of b+2 is discarded print(a, b) --> 1 2 a, b, c = 0 print(a, b, c) --> 0 nil nil ~~~ 上面的例子中,最后一条赋值语句是一个常犯的错误。如果要初始化一系列的变量为0,那么必须为每一个变量提供一个值。 ~~~ a, b, c = 0, 0, 0 print(a, b, c) --> 0 0 0 ~~~ 上面的例子,基本是人为的写出来说明多赋值的用法,在程序中,我们通常是不用多赋值语句来对一系列不相干的变量在同一行上进行赋值。多赋值操作在执行速度上不如同样结果的单赋值语句。但是,有时我们确实需要使用多赋值语句,例如上面的交换两个变量的值。还有一个用途是,可以获取函数的多个返回值。这种情况下,就是一个表达式为多个变量提供值。例如,a, b = f(),对函数f的调用返回两个值:a得到第一个,b得到第二个。 ## 2. 局部变量和语句块 除了全局变量,Lua也支持局部变量。可以用**local**来创建局部变量 ~~~ j = 10 -- global variable local i = 1 -- local variable ~~~ 跟全局变量不同,局部变量有作用域,只在它被声明的域内有效。域可以是一个控制结构,一个函数,或者代码块(变量声明的文件或者代码块) ~~~ x = 10 local i = 1 -- local to the chunk while i <= x do local x = i*2 -- local to the while body print(x) --> 2, 4, 6, 8, ... i = i + 1 end if i > 20 then local x -- local to the then body x = 20 print(x + 2) --(would print 22 if test succeeded) else print(x) --> 10 (the global one) end print(x) --> (the global one) ~~~ 如果上面的示例在交互模式下运行,可能会得不到预期的结果。在交互模式下,每一行是一个块。一旦你输入示例的第2行(local i = 1),Lua立刻运行它并且在下一行开始一个新的块。那时,**local**声明已经无效。当然,解决这个问题的办法肯定是有的。我们可以用**do-end**来显式地对整个块取消限制。只要你输入了**do**,那么命令只有在得到相对于的**end**时才会完成,因此Lua不会自己执行每一行。 在你想更好的控制局部变量的作用域时,**do**语句块是非常好用的: ~~~ do local a2 = 2*a local d = (b^2 - 4*a*c)^(1/2) x1 = (-b + d)/a2 x2 = (-b - d)/a2 end -- scope of 'a2' and 'd' ends here print(x1, x2) ~~~ 尽可能的使用局部变量,是一个好的编程方式。局部变量可以使你避免用很多多余的全局变量名字导致的程序混乱。除此之外,局部变量的访问速度要比全局变量快。还有,局部变量在超出作用域后会自动失效,垃圾收集器会释放掉它占用的空间。 Lua将局部变量声明看成是语句。这样,你可以在任何地方code局部变量声明。局部变量的作用域,从它声明的地方开始生效,到语句块结束时失效。每条声明语句可以包含一个初始化,像常规的初始化语句一样:多余的值被丢弃,多余的变量赋值为nil。如果声明不包含初始化赋值,那么就会默认初始化为nil: ~~~ local a, b = 1, 20 if a < b then print(a) --> 1 local a -- '=nil' is implicit print(a) --> nil end print(a, b) -->1 10 ~~~ 一个很常用的Lua语句: ~~~ local foo = foo ~~~ 这句代码创建一个局部变量foo,并且用全局变量foo的值来对它进行初始化。(局部的foo在声明后可见)。有时,语句块想保护foo的值,防止后来它的值被其他函数改变,这种情况下,这条语句就很有用了;当然,也可以加快对foo的访问速度。 因为很多语句强制要求所有的局部变量都必须在一个块的开始处进行声明,一些人会认为在代码块中间进行局部变量声明不是好的编程风格。但是,恰恰相反:当你要用的时候再声明它,你总是会附带一个初始化的赋值语句(这样你就很少会忘记变量初始化)。除此之外,也可以缩小变量的作用域,增强代码可读性。 ## 3.控制结构 Lua提供了一些传统的控制结构,**if**条件执行,**while**,**repeat**和**for**迭代执行。所有的控制结构都有一个显式的结束符:**end**是**if**,**for**,**while**的结束符;**until**是**repeat**结构的结束符。 控制语句的条件表达式可能是任意值,Lua将**false**和**nil**看作是**false**,其他所有都是**true**。 ### if then else **if**语句检查它的条件表达式,根据结果来执行then-part或者else-part。else-part是可选的。 ~~~ if a < 0 then a = 0 end if a < b then return a else return b end if line > MAXLINES then showpage() line = 0 end ~~~ 要写嵌套的**if**,可以使用**elseif**。它类似于**else if**,但是这样可以省略不写多个**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没有**switch**语句,因此上面这样长的if语句是比较平常的。 ### while **while**检查条件表达式,如果条件为**false**,那么循环停止;否则,Lua运行循环体。然后重复这个过程。 ~~~ local i = 1 while a[i] do print(a[i]) i = i + 1 end ~~~ ### repeat 从名字就可以看出,**repeat-until**语句会执行它的循环体,直到它的条件表达式为**true**。条件表达式实在循环体之后才检查,因此,循环体至少会被运行一次。 ~~~ -- print the first non-empty input line repeat line = io.read() until line ~= "" print(line) ~~~ 跟其他大多数语言不同,在Lua中,在循环体中声明的局部变量的作用域包括条件检查部分: ~~~ local sqr = x/2 repeat sqr = (sqr + x/sqr)/2 local error = math.abs(sqr^2 - x) until error < x/10000 -- 'error' still visible here ~~~ ### Numeric for 在Lua中,for循环有两种形式,**numeric for**和**generic for** **numeric for**的语法如下: ~~~ for var = exp1, exp2, exp3 do end ~~~ 这个for循环,用var的值(从exp1到exp2,步进为exp3,exp3缺省默认为1)来执行something循环体。一些代表性的示例如下: ~~~ for i = 1, f(x) do print(i) end for i = 10, 1, -1 do print(i) end ~~~ 如果你想无上限的不停的循环,那么你可以用常量*math.huge*: ~~~ for i = 1, math.huge do if (0.3*i^3 - 20*i^2 - 500 >= 0) then print(i) break end end ~~~ for循环有一些技巧,我们要学习它们以便更好地使用for循环。第一,3个表达式exp1,exp2,exp3只会在循环开始的时候被计算1次。例如,在之前的示例中,f(x)只被调用一次。第二,控制变量被for语句自动声明为局部变量,并且只在循环中有效。下面的示例说明了一个代表性的错误,认为控制变量i在循环结束后仍然有效: ~~~ for i = 1, 10 do print(i) end max = i -- probably wrong! 'i' here is global ~~~ 如若在循环结束后需要知道控制变量的是值,那么就必须将它的值保存到另外的变量中: ~~~ -- find a value in a list local found = nil for i = 1, #a do if a[i] < 0 then found = i -- save value of 'i' break end end print(found) ~~~ 第三,绝对不能去更改控制变量的值,否则结果是不可预料的。如果想提前结束一个for循环,可以使用break。 ### Generic for **generic for**循环从一个迭代函数中遍历所有的值 ~~~ -- print all values of array 'a' for i, v in ipairs(a) do print(v) end ~~~ Lua基础库提供了一个顺手的迭代函数*ipairs*。每次循环,i得到index,v得到与index相关的值。下面是一个类似的遍历table中的所有key的示例: ~~~ -- print all keys of table 't' for k in pairs(t) do print(k) end ~~~ 从表面上看很简单,但是generic for是比表面上看更强大的。使用合适的迭代器,我们可以遍历几乎任何东西。基础库提供了几个迭代器,迭代文件的行(*io.lines*),table的key-value对(*pairs*),数组中的条目(*ipairs*),string中的字符(*string.gmatch*),等等。当然,我们也可以自己写迭代器。尽管使用generic for很容易,但是写迭代器还是有一些技巧的,这个后面再讨论。 generic for和numeric for有两个相同点:控制变量是循环体中的局部变量,绝不能对控制变量赋值。 让我们再看一个更具体的generic for的示例,假设一个table如下: ~~~ days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} ~~~ 现在,要把名字转换为其在一周中的位置,例如Sundays转换为1.你可以搜索table,找到相应的name。但是一个更有效率的方法是创建一个reverse table,表名为revDays,以name为key,number为值,看起来应该如下: ~~~ revDays = {["Sundays"] = 1, ["Mondays"] = 2, ["Tuesday"] = 3, ["Wednesday"] = 4, ["Thursday"] = 5, ["Friday"] = 6, ["Saturday"] = 7} ~~~ 那么,你只需要以name为key从revDays中取值就可以了 ~~~ x = "Tuesday" print(revDays[x]) --> 3 ~~~ 当然,我们不需要手动声明这个revDays,我们可以根据原来的Days来创建它: ~~~ revDays = {} for k, v in pairs(days) do revDays[v] = k end ~~~ ### break 和 return **break**和**return**语句可以使程序跳出循环 我们用**break**语句结束一个循环,结束包含它的最内层的循环,这个跟C是类似的。不能在循环体外使用**break**。**break**后,程序继续从跳出的循环部分继续执行。 **return**语句从函数返回结果,或者简单地只是结束一个函数。在函数的末尾都有一个隐式的**return**,因此,如果函数正常结束,不返回任何值,就没必要显式地调用一次**return**。 由于一些语法限制,**break**和**return**语句只能作为一个语句块的最后一句(这个在5.2版本已经没有限制了)。下面是一个示例: ~~~ local i = 1 while a[i] do if a[i] == v then break end i = i + 1 end ~~~ 有时候,在语句块的中间**return**或者**break**也很有必要,例如调试的时候(如下示例),那可以用*do end*来显示构造一个语句块 ~~~ function foo() return --<< SYNTAX ERROR -- 'return' is the last statement in the next block do return end -- ok end ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    表达式

    最后更新于:2022-04-01 20:44:04

    在Lua中,表达式包括:数值常量、字符串字面值、变量、单目和双目运算符,函数调用,也包括一些非传统的函数定义和表结构。 ## 1.算术运算符 Lua支持常规的算术运算符:'+', '-', '*', '/', '^', '%', '-'(负)。所有这些运算符都对实数起作用。举个例子,x^0.5, 计算x的平方根, x^(-1/3),计算x的立方根的反数。 在Lua中,'%'运算符定义规则如下: a%b == a - floor(a/b)*b 对整型参数来说,它表示常规的意义,结果跟第二个参数b符号相同。 对实数来说,它有一些额外的用途。例如,x%1 表示x的小数部分,x-x%1 表示x的整数部分。类似的,x-x%0.01 将x精确到小数点后2位。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef244ae.PNG) ## 2.关系操作符 Lua提供了如下关系操作符:       <    >    <=    >=    ==    ~= 所有这些操作符产生的结果都是true或者false '=='检查是否相等,'~='检查是否不等。这两个操作符可以作用于任意的两个值。如果要比较的两个值是不同的类型,那么Lua认为它们是不等的。否则,Lua根据它们的类型来比较它们是否相等。特例,nil只等于它本身。 Lua根据引用来比较table, userdata, function,也就是说,只有二者是同一个对象的时候,它们才相等。例如: ~~~ a = {}; a.x = 1; a.y = 0 b = {}; b.x = 1; b.y = 0 c = a ~~~ 执行下上面的代码,你可以得到, a == c, 但是 a ~= b ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef395a4.PNG) 我们把<, >, <=, >=称为order operator。 order operator只能应用于数值和字符串。Lua根据字母表顺序来比较字符串的大小,字母表的顺序由本地设置决定。非数值和字符串的值只能比较相等或者不相等,不能用于order operator。 当比较两个不同类型的值时,一定要小心:记住"0"和0是不同的。除此以外,2<15显然是true的,但是"2"<"15"是false的(根据字母表顺序比较)。为了避免不一致的结果,当你将string和number混合使用到order operator时,例如 2<"15",Lua会产生一个runtime error。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef4c90d.PNG) ## 3.逻辑运算符 逻辑运算符有:and, or, not。跟控制结构(if,while等)类似,所有的逻辑操作符把false和nil认为是false,其他的任意值都为true。 and:如果第一个参数为false,返回第一个参数,否则返回第二个参数。 or   :如果第一个参数为false,返回第二个参数,否则返回第一个参数。 ~~~ 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 ~~~ 'and'和'or'都是用short-cut计算方式,也即是说,只有在有需要的时候,才会去计算第二个参数的值。这样保证了类似(type(v)=="table" and v.tag=="h1")这样的表达式不会产生run-time error。(当v不是table类型时,Lua不会去计算v.tag的值)。 有用的Lua表达式:x = x or v,等价于: if not x then x = v end (加上x没有被赋值为false),如果x没有被设置,那么给它个默认值v。 另一个有用的Lua表达式:a and b or c,等价于C表达式 a ? b : c,(假设b不为false)。例如,我们可以用下面的语句来得到x和y的最大值。 max = (x >y) and x or y 当 x>y,第一个and表达式结果为第二个参数x,x总是true(因为x为一个数值),那么or表达式的结果为x or y,结果为x。 当 x true print(not false) --> true print(not 0) --> false print(not not nil) --> false ~~~ ## 4.连接操作符 Lua用'..'(两个点)来表示string连接符。如果它的任一个操作数为数字,Lua会把它转为string。 ~~~ print("Hello " .. "World") --> Hello World print(0 .. 1) --> 01 ~~~ 不要忘了,Lua中的string是常量。string连接操作符会创建一个新的string,不会对它的操作数有任何修改: ~~~ a = "Hello" print(a .. " World") --> Hello World print(a) --> Hello ~~~ ## 5.优先级 Lua中的操作符优先级,如下表所示,从高到低 ~~~ ^ not # -(单目) * / % + - .. < > <= >= ~= == and or ~~~ 除了'^和'..',所有的双目操作符都是左关联的。因此,下表中,左面的表达式和右面的表达式是等价的: ~~~ a+i < b/2+1 <--> (a+i) < ((b/2)+1) 5+x^2*8 <--> 5+((x^2)*8) a (a -(x^2) x^y^z <--> x^(y^z) ~~~ 在有疑问的时候,就用括号'()',这比查手册更方便和简单,而且,当你以后看代码的时候,你可能仍然有同样的疑问,显式地使用括号'()'会更直观一点。 ## 6.表构造函数 构造函数是一个用来初始化表(table)的表达式。它是Lua的一个独特特性和很强大的功能之一。 最简单的构造函数是空构造函数,{},创建一个空table。构造函数也可以初始化数组,例如: ~~~ days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} ~~~ 将day[1]初始化为"Sunday", day[2]为"Monday",注意,index是从1开始。 ~~~ print(day[4]) --> Wednesday ~~~ Lua提供一个专门的语法去初始化record-like table,如下: ~~~ a = {x=10, y=20} ~~~ 等价于 ~~~ a = {}, a.x = 10; a.y = 20 ~~~ 不管我们用什么构造函数去创建一个table,我们都可以增加或减少结果中的field: ~~~ w = {x=0, y=0, label="console"} x = {math.sin(0), math.sin(1), math.sin(2)} w[1] = "another filed" -- add key 1 to table 'w' x.f = w -- add key 'f' to table 'x' print(w["x"]) --> 0 print(w[1]) --> another field print(x.f[1]) --> another field w.x = nil -- remove field 'x' ~~~ 也就是说,table创建过程是相同的。构造函数只是将其初始化而已。 Lua每次使用构造函数,都会创建并初始化一个新的table。因此,我们可以用table来实现一个list: ~~~ list = nil for line in io.lines() do list = {next=list, value=line} end ~~~ 这些代码从stdin读入行,然后存入一个list中,顺序跟读入的顺序相反。list中的每个node有两个field:value,包含行的内容,next,指向下一个node。下面的代码遍历这个list,并且打印它的内容: ~~~ local l = list while l do print(l.value) l = l.next end ~~~ 以上两段代码的示例: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef6281e.PNG) (由于我们在栈上实现了list,打印的时候会是逆序的)。尽管实现很方便,但是我们在Lua程序中很少使用如上实现的list;List可以作为array得到更好的实现,我们会在后面再讲这个问题。 我们可在一个构造函数中同时使用record-style和list_style的初始化方式: ~~~ polyline = {color="blue", thickness=2, npoints=4,            {x=0,   y=0},            {x=-10, y=0},            {x=-10, y=1},            {x=0,   y=1}          } ~~~ 上面的例子也说明了如何使用嵌套的构造函数来表示更复杂的数据结构。每一个polyline[i]都是一个table,表示一条记录(record): ~~~ print(polyline[2].x) --> -10 --此处要注意index 1是从哪里开始的,下面的示例may be helpful print(polyline[4].y) --> 1 ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef7782b.PNG) 这两种形式的构造函数(record-style和list_style)都有一些局限性。一些负数或者string不能作为标识符。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef8d0ff.PNG) 因此,Lua提供了一种更通用的格式。在这个格式中,我们显式的写出每一个[index]。 ~~~ opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"} i = 20; s = "-" a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s} print(opnames[s]) --> sub print(a[22]) --> --- ~~~ 这个规则有点麻烦,但是更方便灵活:record-style和list_style是这个通用格式的两个特例而已。 ~~~ {x=0, y=0} <==> {["x"]=0, ["y"]=0} {"r", "g", "b"} <==> {[1]="r", [2]="g", [3]="b"} ~~~ 有时候,你想要你的array的index从0开始(Lua默认从1开始),用下面的方式就可以了: ~~~ days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} ~~~ 现在,第一个值"Sunday", index为0 。它不会影响其他的field;"Monday"的index为1,因为它是构造函数中的第一个list值;其他的跟随。尽管实现很便利,但是不推荐这样用。Lua的很多内置函数都假设array的index从1开始,如果一个array从0开始,可能会在处理上有问题。 在最后一个元素后面,可以加逗号','。这个末尾的逗号','是可选的,但都是合法的: ~~~ a = {[1]="red", [2]="green", [3]="blue",} ~~~ 通过这个便利性,程序在生成Lua table的时候不需要对最后一个元素做特殊处理。 最后,我们可以使用分号';'来代替逗号',',你可以在用';'来分割table中的不同属性的部分,例如: ~~~ {x=10, y=45; "one", "two", "three"} ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    类型和值(二)

    最后更新于:2022-04-01 20:44:02

    ## 5.Table Lua的table类型比较强大,用过都知道,它的index不仅可以使用number,还能使用string等其他的值,nil除外。table没有固定的size,你可以动态地向table中添加元素。table是Lua中的主要数据结构。在Lua中,我们用table来表示普通数组,符号表,set,record,queue和其他的lua数据结构。Lua也使用table来表示module,package和其他的对象。当我们写io.read时,我们的意思是“io模块的read函数”,对Lua来说,它理解为“从名为io的table中以‘read’为key进行索引”。 在Lua中,table既不是值,也不是变量,而是对象。你可以理解成一个动态分配的对象,程序通过引用(或指针)来操作它。在后台没有隐藏的副本或者新table的创建(像按值传参那样,操作的是一个新复制的副本)。要创建一个table,只需要一个简单的表达式{ }: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee9a2e2.PNG) table总是匿名的。table本身和存储table的变量,二者之间没有固定的联系: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eeeb1a67.PNG) 当程序里不再引用一个table的时候,Lua的垃圾收集器会删除这个table并重用它的内存。 同一个table中可以存储不同index类型的值,并且table的大小可以动态增长 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eeecc9c6.PNG) 注意最后一行,像全局变量一样,如果table中未初始化的field值为nil,可以通过赋值nil的方式来删除一个table. 可以在Lua中使用record方式来索引值,a.name这种格式,相当于a["name"]. ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eeee5418.PNG) 注意,a.x==a["x"] != a[x] Lua的table的index值从1开始,不是从0,这个要格外注意 下图描述一下Lua的size运算符'#'的用法,它是以table中第一个值为nil的位置为end的,就像C语言的string是以'\0'为end一样,所以,用'#'的话,table中就不能有hole(值为nil的),仔细看下面的图,你会明白的。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eef09993.PNG) 注意最后两句,可以看出'#'和'table.maxn()'的功力。 因为a中第4个位置为nil,因此用'#'算出来就是3,而table.maxn就可以得到5. table.maxn函数打印最大的数字index值 ## 6.Functions Function在lua中是属于first-class值,可以存储到变量中,可以作为参数传递给其他的function,可以作为function的result来return。 Lua中,可以调用用lua或C语言实现的函数。Lua本身就是用C实现的。它实现了string,table,io,math,debug等基本的库,程序可以自己定义其他的函数来扩展功能。 这部分在后续的博客中会继续深入挖掘一下 ## 7.Userdata和Threads userdata类型允许任意的C数据存入到lua的变量中,关于此类型,Lua中没有什么预定义的操作,除了复制和相等比较。此类型是为了表示由程序创建的一些数据类型;例如,io库用userdata类型来表示file。 具体的userdata和thread相关,后续随着学习进行会继续深入挖掘。 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    类型和值(一)

    最后更新于:2022-04-01 20:43:59

    Lua是一个动态类型的语言,没有类型定义,每个值都有自己的类型。 Lua有8个基本类型,nil, boolean, number, string, userdata, function, thread, table,利用type函数可以打印出一个给定值的类型。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed8bab5.PNG) 注意最后一行,无论x为什么值,结果一定是string,因为type函数的结果总是一个string。 变量没有预定义的类型,任何变量可以有任何类型的值 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed9f100.PNG) 注意最后两行,函数也是可以用来赋值的,你可以像对待其他的变量一样来把函数当成一个变量。 通常情况下,如果你把一个变量用来包含不同类型的值,会使代码看起来很混乱。但是,有时有效地利用这个特性,会有意想不到的效果。例如使用nil来对函数的返回值就行判断,来区分一个正常的返回值还是一个异常条件。 ## 1 Nil Nil类型只有一个值,nil,跟其他任何值都不同。如我们之前看到的,在一个全局变量未被赋值之前,它的值为nil,你也可以通过将一个全局变量赋值为nil来删除它。Lua用nil作为一个无值类型,表示一个变量不再起作用。 ## 2 Booleans Boolean类型含有两个值,true和false。但是Lua中可以表示条件的变量不仅仅boolean类型,任何值都可以表示条件。Lua认为false和nil为"假",其他任何值都是"真"。注意,跟其他语言不同的是,Lua认为0和空字符串为"真"。 ## 3 Numbers Number类型代表实数(在计算机中占两个地址的浮点数)。Lua没有整型,并且也不需要。有个普遍的误解,使用浮点型会有算术错误,一些人害怕浮点型即使一个简单的“加”运算也会有奇奇怪怪的问题。但事实上,用一个double类型来表示一个integer,不存在任何边界问题(除非这个数大于1.0e14)。Lua可以表示任何的32-bit的整数,不存在任何边界问题。除此之外,大多数cpu处理浮点数会比整数要快。 不过,Lua为了编译方便,也使用了其他的number类型,例如long和单浮点数。这是为了兼容一些不支持double类型的设备。 以下这些numeric常量都是合法的 4 0.4 4.57e-3 0.3e12 5e+20 ## 4 Strings String在Lua中与其他编程语言没有什么不同,都是表示一串字符。但是Lua可以在字符串中含有任何字符,包括0. String在Lua中不可被更改,是常量。你不能更改一个字符串中的某一个字符,但是可以创建一个新的string。例如 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eedb1d42.PNG) Lua的string是自动管理内存的,其他的lua对象(例如table, function,etc)也一样。这意味着你不必担心string的内存分配与释放,让Lua为你处理这些问题就好了。一个string可以包含一个字符,也可以包含一整本书,Lua可以有效地处理长string,处理100k-1M的字符串,对Lua来说是小菜一碟。 ### 4.1 Lua string的划界 上面说了,Lua的string可以包含任何字符,甚至0(在C中,0是被认为是string的结尾的)。因此Lua对字符串边界的判定,不能用0,而是匹配单引号'或者双引号" : ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eedc53ba.PNG) 但是在编程的时候,为了风格统一,最后是只用其中一种,除非string本身含有引号。或者在字符串中用转义字符。Lua的string可以使用以下C风格的转义字符: \a     bell \b     back space \f     form feed \n    newline \r     carriage return \t     horizontal tab \v     vertical tab \\     backslash \"     double quote \'      single quote 示例如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eedd9dc7.PNG) 我们可以在string中用一个字符的ASCII值来表示它,格式为\ddd,这里ddd最多包含3个数字字符,例如: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eedefecd.PNG) 97表示"a", 10表示new line,49表示数字"1",注意\049没有写成\49的原因是,后面跟的字符是数字,若写成\49,那么会被认为是\492,这就不是我们想要的了。 对string划界,除了匹配引号之外,我们可以像处理长注释一样,匹配两个“[[”“]]”。使用这种划界方式的string通常会有几行,如果第一个字符是“换行符”,则会被忽略掉,并且不会解释转义字符(如下示例),保持原样输出。通常在string中包含代码片段的时候使用比较方便。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee10a5e.PNG) 这个时候,有个疑问,如果我的string中含有“[[”或者“]]”,怎么办。Lua提供了一个更方便的解决办法,就是使用类似[====[这样的匹配符号,“=”个数任意,但是保证前后相同,如果“=”个数不同,那么就会被忽略掉。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee2645f.PNG) 这种方式也可以用在长注释中,这里就不演示了。 ### 4.2 Lua中string与number的相互转换 Lua提供了string和number的运行时自动转换。任何对string进行的算术操作,都会尝试将string转换为number。例如: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee3dfc1.PNG) 除了算术操作,其他任何期待一个number的地方,如果传入string,都是尝试将string转换为number。相反,在一个期待string的地方,如果传入number,那么也会尝试将number转换为string。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee52f2d.PNG) “..”是Lua中的字符串连接符,在10之后要有一个空格“ ”,否则第一个“.”会被看作是小数点。 现在看来,在Lua的设计中,支持这些自动转换是不是一个good idea,都没有一个定论。但是,不要太指望它们。它们在少数场合用起来很方便,但是会增加语言的复杂性,增加使用它们的程序的复杂性。毕竟string和number是两个不同的东西。10 == “10”是false,因为10是一个number,而“10”是一个string。如果你想把string转换为number,显式地用函数tonumber,如果要转换的string不能被转为number,它返回nil。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee6eba6.PNG) 将number转换为string,可以用函数tostring。另外,可以用“#”来取得一个string的长度。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eee832f8.PNG) 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    基本介绍

    最后更新于:2022-04-01 20:43:57

    ## 1. Lua 命名规则 跟C语言一样,有字母数字下划线3个元素组合,不能以数字开头,下面这些命名都是合法的 i,  j,    i10,     _ij,      aSomewhatLongName, _INPUT 注意最后一个_INPUT,在Lua中要尽量避免使用以下划线"_"开头,后面跟大写字母的命名,因为这些通常会作为保留字有特殊用途。 Lua是大小写敏感的,例如name与Name就绝对是不等的。 Lua保留字列表 and  break  do  else  elseif  end  false  for  function  if  in  local  nil   not   or repeat  return  then  true  until  while ## 2. Lua注释规则 单行注释:-- 开始到行尾 多行注释:--[[开始,到 ]] 结束。 多行注释掉妙用 --[[ statements...... --]] 在需要打开整个注释掉时候,可以简单地在第一行前加一个“-”就可以了,这样标识注释起始和结束位置的行就分别变成了单行注释,如下 ---[[ statements....... --]] ## 3. Lua的全局变量 全局变量不需要声明,你只需要给他一个值来创建它。访问一个未初始化的变量是合法的,只是结果会是nil ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed354f0.PNG) 上图中的最后两句,删除一个全局变量b,在 b = nil 这一句之后,b对lua来说,就是不存在的变量了。换句话说,lua的全局变量只有在它的值不为nil时存在。 ## 4.  系统无关的解析器 lua的解析器如果加载了一个文件,文件第一行为#开头,那么第一行会被忽略掉。这个特性保证了Lua在Unix系统下的正常运行,例如 ~~~ #!/usr/local/bin/lua        or           #!/usr/bin/env lua ~~~ Lua 的用法 lua [options] [script [args]] everything都是可选的。如前面我们看到的,如果我们不带任何参数和选项去执行lua,那么会进入交互模式。 -e选项,运行用户在命令行中直接写脚本语句 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed480b2.PNG) -i 选项,进入交互模式, 利用全局变量_PROMPT来改变交互模式的提示符 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed5b0c1.PNG) 提示符变成了CARL_LUA> ## 5. lua的脚本参数规则: 代码如下: ~~~ print("arg[-3]=" , arg[-3]) print("arg[-2]=" , arg[-2]) print("arg[-1]=" , arg[-1]) print("arg[0]=" , arg[0]) print("arg[1]=" , arg[1]) print("arg[2]=" , arg[2]) ~~~ 运行结果如下,注意索引的对应值,基本就是.lua文件为0,往后++,往前-- ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed73234.PNG) 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    初识Lua

    最后更新于:2022-04-01 20:43:55

    跟学习其他的编程语言一样,学习Lua从hello world开始。 新建一个文件,hello.lua,内容为 print("hello world")。 在shell界面,输入lua hello.lua ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eec9ef5c.PNG) 下面定义个函数 新建一个文件 func_test.lua 内容如下: ~~~ -- define a factorial funcition function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("enter a number:"); a=io.read("*number") -- read a number print(fact(a)) ~~~ 运行 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eecb77ff.PNG) 关于语句块 lua在连续语句之间是不需要分隔符的,例如下面的4个语句块是等价的。 ~~~ a = 1 b = a*2 a = 1; b = a*2; a = 1; b = a*2; a = 1 b = a*2          -- ugly, but valid ~~~ 上面的两个示例,我们是将code写到文件中去运行的,还有另一种方式也可以运行lua语句,就是在交互模式下 在shell模式下,输入lua,不带任何参数,会进入交互模式 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eeccb2d1.PNG) 要退出交互模式,可以用ctrl+d或者输入os.exit() 在得到上图所示的状态后,可以直接输入lua语句运行,lua会把每一行当成一个完整的块来对待,如果它检测到一行构不成一个完整的块,那么它会等待块输入完成。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eecdea8d.PNG) 在交互模式下,也可以通过dofile函数来执行lua脚本文件,也可以在执行了一个文件以后通过 -i 选项让lua进入到交互模式,示例如下,我们将上面的func_test.lua改成lib.lua,并将最后3行注释掉 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed0084f.PNG)            ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-06_57ce5eed159bb.PNG) 水平有限,如果有朋友发现错误,欢迎留言交流。
    ';

    学习开篇

    最后更新于:2022-04-01 20:43:53

    接触lua是一次很偶然的机会,主要用了lua脚本跟C/C++交互的特性。没有深入研究,根基不牢。近期打算从基础开始,慢慢地来体会这门脚本语言。 学习环境fedora,lua版本5.1.4,学习资料《Programming in Lua, 2ND Edition》 文章中的例子,博主都亲手去运行过。 目前完成的基础学习部分文章如下: [Lua基础 初识Lua](http://blog.csdn.net/wzzfeitian/article/details/8276104) [Lua基础 基本介绍](http://blog.csdn.net/wzzfeitian/article/details/8279926) [Lua基础 类型和值(一)](http://blog.csdn.net/wzzfeitian/article/details/8292130) [Lua基础 类型和值(二)](http://blog.csdn.net/wzzfeitian/article/details/8349441) [Lua基础 表达式](http://blog.csdn.net/wzzfeitian/article/details/8633206) [Lua基础 语句](http://blog.csdn.net/wzzfeitian/article/details/8641641) [Lua基础 函数(一)](http://blog.csdn.net/wzzfeitian/article/details/8653101) [Lua基础 函数(二)](http://blog.csdn.net/wzzfeitian/article/details/8701747) [Lua基础 generic for](http://blog.csdn.net/wzzfeitian/article/details/8738552) [Lua基础 编译、运行、错误处理](http://blog.csdn.net/wzzfeitian/article/details/8789443) [Lua基础 安装LuaSocket](http://blog.csdn.net/wzzfeitian/article/details/8866390) [Lua基础 coroutine —— Lua的多线程编程](http://blog.csdn.net/wzzfeitian/article/details/8832017) 未完待续。。。
    ';

    前言

    最后更新于:2022-04-01 20:43:50

    > 原文出处:[Lua入门与进阶](http://blog.csdn.net/column/details/lua-programming.html) 作者:[wzzfeitian](http://blog.csdn.net/wzzfeitian) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # Lua入门与进阶 > 根据Lua官方教程,结合自身代码+运行结果,通俗地表述Lua的用法及其强大
    ';