Lua程序设计
迭代器和泛型for
迭代器和闭包
1.迭代器的定义
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型for语句
1. 泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in ipairs(t) do
print(k, v)
end
demo:
array = {"Lua", "Tutorial"}
for key,value in ipairs(array)
do
print(key, value)
end
for line in io.lines() do
io.write(line, '\n')
end
or <var-list> in <exp-list> do
<body>
end
2. 范性for的执行过程:
首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数
无状态的迭代器
1. 无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。
2. 实现一个简单的ipairs迭代器
unction 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
多状态的迭代器
1. 很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。
2. 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 -- 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
for word in allwords() do
print(word)
end
真正的迭代器
1. 迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是for语句,也许更好的叫法应该是生成器(generator);但是在其他语言比如java、C++迭代器的说法已经很普遍了,我们也就沿用这个术语。
有一种方式创建一个在内部完成迭代的迭代器。这样当我们使用迭代器的时候就不需要使用循环了;我们仅仅使用每一次迭代需要处理的任务作为参数调用迭代器即可,具体地说,迭代器接受一个函数作为参数,并且这个函数在迭代器内部被调用。
2. demo
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
ocal count = 0
allwords(function (w)
if w == "hello" then count = count + 1 end
end)
print(count)
编译运行-错误消息
require函数
1. 主要用来加载函数库
2.require &dofile的对比
1)require会搜索目录加载文件
2)require会判断是否文件已经加载避免重复加载同一文件。由于上述特征,require在Lua中是加载库的更好的函数
3. require的路径是一个模式列表,每一个模式指明一种由虚文件名(require的参数)转成实文件名的方法。
举例:模式?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
dofile "lili"
匹配的路径为
lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua
C packages
1. Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。
loadlib(path, "luaopen_socket") 绝对路径,和初始化函数。
2. loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so"
-- or path = "C:\\windows\\luasocket.dll"
local f = assert(loadlib(path, "luaopen_socket"))
f() -- actually open the library
错误
1. assert函数
首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。第二个参数是可选的。
2. demo
print "enter a number:"
n = assert(io.read("*number"), "invalid input")
异常和错误处理
1. 通常应用要求Lua运行一段chunk,如果发生异常,应用根据Lua返回的错误代码进行处理。如果在Lua中需要处理错误,需要使用pcall函数封装你的代码。
2. demo
step:1 这段代码封装在一个函数内
function foo ()
...
if unexpected_condition then error() end
...
print(a[i]) -- potential error: `a' may not be a table
...
end
step:2 使用pcall调用这个函数
if pcall(foo) then
-- no errors while running `foo'
...
else
-- `foo' raised an error: take appropriate actions
...
end
3. pcall在保护模式(protected mode)下执行函数内容,同时捕获所有的异常和错误。若一切正常,pcall返回true以及“被执行函数”的返回值;否则返回nil和错误信息。
错误信息和回跟踪
1. error(string )函数
协同程序
协同程序和线程的区别
1. Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同是非常强大的功能,但是用起来也很复杂。
2. 线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
3. 基本语法
coroutine.create() 创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用
coroutine.resume() 重启coroutine,和create配合使用
coroutine.yield() 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果
coroutine.status() 查看coroutine的状态
注:coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序
coroutine.wrap() 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复
coroutine.running() 返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号
4 demo
-- coroutine_test.lua 文件
co = coroutine.create(
function(i)
print(i);
end
)
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
5. demo
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
管道和过滤器
1. 生产者和消费者问题
demo:假定有一个函数不断地生产数据(比如从文件中读取),另一个函数不断的处理这些数据(比如写到另一文件中)
local newProductor
function productor()
local i = 0
while true do
i = i + 1
send(i) -- 将生产的物品发送给消费者
end
end
function consumer()
while true do
local i = receive() -- 从生产者那里得到物品
print(i)
end
end
function receive()
local status, value = coroutine.resume(newProductor)
return value
end
function send(x)
coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序
end
-- 启动程序
newProductor = coroutine.create(productor)
consumer()
demo2
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 filter (prod)
return coroutine.create(function ()
local line = 1
while true do
local x = receive(prod) -- get new value
x = string.format("%5d %s", line, x)
send(x) -- send it to consumer
line = line + 1
end
end)
end
function consumer (prod)
while true do
local x = receive(prod) -- get new value
io.write(x, "\n") -- consume new value
end
end
p = producer()
f = filter(p)
consumer(f)
用作迭代器的协同
1. 我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子。迭代函数产生值给循环体消费。所以可以使用协同来实现迭代器。协同的一个关键特征是它可以不断颠倒调用者与被调用者之间的关系,这样我们毫无顾虑的使用它实现一个迭代器,而不用保存迭代函数返回的状态。
2. demo
非抢占式多线程
完整实例
1.demo
--entry.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 <A HREF="http://www.petrobras.com.br/">PETROBRAS</A>,
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------
function fwrite (fmt, ...)
return io.write(string.format(fmt, unpack(arg)))
end
function BEGIN()
io.write([[
<HTML>
<HEAD><TITLE>Projects using Lua</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF">
Here are brief descriptions of some projects around the
world that use <A HREF="home.html">Lua</A>.
]])
end
function entry0 (o)
N=N + 1
local title = o.title or '(no title)'
fwrite('<LI><A HREF="#%d">%s</A>\n', N, title)
end
function entry1 (o)
N=N + 1
local title = o.title or o.org or 'org'
fwrite('<HR>\n<H3>\n')
local href = ''
if o.url then
href = string.format(' HREF="%s"', o.url)
end
fwrite('<A NAME="%d"%s>%s</A>\n', N, href, title)
if o.title and o.org then
fwrite('\n<SMALL><EM>%s</EM></SMALL>', o.org)
end
fwrite('\n</H3>\n')
if o.description then
fwrite('%s', string.gsub(o.description,
'\n\n\n*', '<P>\n'))
fwrite('<P>\n')
end
if o.email then
fwrite('Contact: <A HREF="mailto:%s">%s</A>\n',
o.email, o.contact or o.email)
elseif o.contact then
fwrite('Contact: %s\n', o.contact)
end
end
function END()
fwrite('</BODY></HTML>\n')
end
BEGIN()
N = 0
entry = entry0
fwrite('<UL>\n')
dofile('db.lua')
fwrite('</UL>\n')
N = 0
entry = entry1
dofile('db.lua')
END()