5.3 具名实参
通过名称来指定实参
--os.rename,希望能接受两个具有名称的实参
rename{old="temp.lua",new="temp1.lua"} --table构造式可以省略圆括号
--将rename改为只接受一个参数
function rename(arg)
return os.rename(arg.old,arg.new)
end
如果一个函数拥有大量参数,其中大部分参数是可选的话,这种传递风格会特别有用。
--用于检查参数和设置默认参数值的函数
function Window(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
_Window(options.title,
options.x or 0,
options.y or 0,
options.width,
options.height,
options.background or "white",
options.border
)
end
--真正实现功能的函数
function _Window( x,y,width,height,title,background,border)
print(x)
print(y)
print(width)
print(height)
print(title)
print(background)
print(border)
end
w = Window{x=0,y=0,width=300,height=200,
title = "Lua",background="blue",
border = true
}
6 深入函数
- 函数与其他传统类型的值具有相同的权利。函数可以存储到变量中或table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值
a = {p = print }
a.p("Hello World") --Hello World
print = math.sin --现在 print 是正弦函数
a.p((print(1))) --0.841470
sin = a.p --sin引用print函数
sin(10,20) -- 10 20
--两者一样,前者是后者的简化书写
function foo(x) reutn 2*x end
foo = function (x) return 2*x end
--table.sort,2个参数,前面的是table表,后面的是排序方式,这里是按照name字段、按反向的字符顺序来对这个table排序
table.sort(network,function (a,b) return(a.name>b.name) end)
--高阶函数,求导
function derivative(f,delta)
delta =delta or 1e-4
return funciton (x)
return (f(x+delta)-f(x))/delta
end
end
c = derivative(math.sin)
print(math.cos(1o),c(10)) --两者几乎相等
6.1 closure 闭合函数
函数里嵌套函数,位于内部的函数可以访问外部函数中的局部变量
function sortbygrade(names,grades)
table.sort(names,function(n1,n2)
return grades[n1] > grades[n2] end)
end --在这个匿名函数内部,grades是“非局部的变量”
oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end
--do-end作为一个程序块
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function(x)
return oldSin(x*k)
end
end
6.2 非全局的函数
--三种相同的局部函数声明方式
Lib={}
Lib.foo = function (x,y) return x+y end
Lib.goo = funciton (x,y) return x*y end
Lib={ foo=function (x,y) return x+y end, goo = function(x,y) return x*y end} --table构造式法定义
Lib = {}
function Lib.foo (x,y) return x+y end
function Lib.goo (x,y) return x*y end
local f = function (<参数>)
<函数体>
end
local funtion f (<参数>)
<函数体>
end
--定义递归的局部函数
local fact --一定要先完成fact的定义,然后才能再使用fact进行自身的递归
fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
6.3 正确的尾调用
-
function f(x) return g(x) end
一个函数调用是另一个函数的最后一个动作,该调用时一条“尾调用” - 尾调用,程序不需要保存任何关于该函数的栈信息,当g返回时,执行控制权可以直接返回到调用f的那个点上,使得尾调用不耗费任何占空间,这种实现称为支持“尾调用消除”
- 一些不是尾调用的案例:
funcion f(x) g(x) end --要丢弃g返回的临时结果
return g(x)+1 --必须加法一次
return x or g(x) --必须调整为一个返回值
return (g(x)) --必须调整为一个返回值,注:前面提到圆括号内只能有一个值
- Lua中只有
return <func>(<args>)
这样的调用形式才算尾调用,参数可以是任意复杂的表达式
7.1 迭代器与closure
--普通迭代器
function values(t)
local i = 0
return function ()
i = i + 1
return t[i]
end
end
t = {10,20,30}
iter = values(t)
while true do
local element = iter()
if element == nil then break end
print(element)
end
--读取文件,并逐行查找需要的单词
function allwords()
local line = io.read()
local pos = 1
return function ()
while line do
local s,e = string.find(line,"w$",pos)
if s then
pos = e + 1
return string.sub(line,s,e)
else
line = io.read()
pos = 1
end
end
return nil
end
end
7.3 无状态的迭代器
- ipairs用来迭代数组(关键字key是number)可以输出nil,pairs碰到nil会直接跳过
-- ipairs和pairs的区别 --
--ipairs
-ipairs工厂
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
--pairs工厂
function pairs(t)
return next, t, nil
end
for k, v in next, t do
<loop body>
end
tbl = {"alpha", "beta", [3] = "uno", ["two"] = "dos"}
for i,v in ipairs(tbl) do --输出前三个(到第一个不是数字的key
print( tbl[i] )
end
for i,v in pairs(tbl) do --全部输出
print( tbl[i] )
end
- 可以不通过pairs调用直接使用next,nil可以不写,lua中自动将for循环中表达式列表的结果调整为3个值
for k, v in next, t do <loop body> end
- 无状态迭代器可以遍历链表的迭代器
local function getnext(list, node)
if not node then
return list
else
return node.next
end
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
7.4具有复杂状态的迭代器
- 循环过程中恒定状态总是同一个table,但这个table 的内容可以发生改变,通常可以忽略泛型for提供的第二个参数(控制变量
- 迭代器重写allwords
local iterator
function allwords()
local state = {line = io.read(), pos = 1}
return iterator,state
end
function iterator(state)
while state.line do
local s, e = string.find(state.line,"%w+", state.pos)
if s then
state.pos = e + 1
return string.sub(state.line,s,e)
else
state.line = io.read()
state.pos = 1
end
end
return nil
end
7.5 真正的迭代器
迭代器只是为每次迭代提供一些成功后的返回值,更准确的叫“生成器”
function allwords(f)
for line in io.lines() do
for word in string.gmatch(line,"%w+") do
f(word)
end
end
end
--这个迭代器,需要传入一个描述循环体的函数
--还可以使用一个匿名函数作为循环体
local count = 0
allwords(function(w) if w == "hello" then count = count + 1 end end)
print(count)
8 编译、执行与错误
dofile与loadfile的区别
loadfile不会引发错误,只是返回错误值并不处理错误,
如果需要多次运行一个文件,那么只需在调用一次loadfile后,多次调用它的返回结果。
loadstring与loadfile类似,前者是从一个字符串中读取代码,后者是从文件中读取
f = loadstring(“i = i + 1”)
f变成了一个函数,每次调用时就执行“i = i + 1”
开销较大慎用,并且可能会导致难以理解的代码。
i = 32
local i = 0
f = loadstring("i = i + 1; print(i)")
g = function() i = i + 1 print(i) end
f() -->33
g() -->1
loadstring总是在全局环境中编译它的字符串
8.3 错误
print"enter a number:"
n = assert(io.read("*number"),"invalid input")
assert函数检查第一个参数是否为true。若为true则简单地返回该参数;否侧引发一个错误。第二个参数是一个可选的信息字符串
函数io.open,用于打开一个文件们如果一个文件不存在,那么它应返回nil,并附加一条错误消息,就可以采取适当的做法来处理异常情况
local file, msg
repeat
print"enter a file name:"
local name = io.read()
if not name then return end --无输入
file, msg = io.open(name,"r")
if not file then print(msg) end --如果file为nil,那么打印错误消息
until file
在这里我们可以使用assert来检测操作
file = assert(io.open(name,"r"))
如果io.open失败,assert就引发一个错误
8.4 错误处理与异常
所有的Lua活动都是由应用程序的一次调用而开始的,这类调用通常是要求Lua执行一个程序块。如果执行中发生了错误,此调用会返回一个错误代码,这样应用程序就能采取适当的行动来处理错误。
如果需要在Lua中处理错误,则必须使用函数pcall来包装需要执行的代码
function foo()
<some code>
if inexpectant conditions then error() end
<some code>
print(a[i])
<some code>
end
--使用pcall来调用foo
if pcall(foo) then
<常规代码>
else
<错误处理代码>
end
9 协同程序
与线程差不多,一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他协同程序共享全局变量和其他大部分东西
9.1 协同程序基础
co = coroutine.create(function() print("hi") end)
print(co) -->thread:0x8071d98
一个协同程序可以出于4种不同状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)
9.2 管道和过滤器
--消费者-生产者问题
function producer()
while true do
local x = io.read()
send(x) --发送给消费者,将x挂起
end
end
function consumer()
while true do
local x = receive()
io.write(x,"\n")
end
end
function receive()
local status, value = coroutine.resume(producer)
return value
end
function send(x)
coroutine.yield(x)
end
producer = coroutine.create(function()
while true do
local x = io.read()
send(x)
end
end)
--通过消费者启动,唤醒生产者,返回一个新值之后停止运行,等待再次唤醒
扩展实现“过滤器”,一种位于生产者和消费者之间的处理功能,它既是一个消费者又是一个生产者,它唤醒生产者促使其产生新值,然后又将变换后的值传递给消费者。
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()
send(x)
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
function consumer(prod)
while true do
local x = receive(prod)
io.write(x,"\n")
end
end
consumer(filter(producer()))
11 数据结构
11.1 数组
Lua中习惯一般是以1作为数组的起始索引。Lua库和长度操作符都遵循这个约定。如果你的数组不是从1开始的,那就无法使用这些功能
11.2 矩阵与多维数组
- 矩阵的表示方式:数组的数组,也就是一个table里每个元素都是另外一个table(跟C差不多,直接声明更烦琐一些
- 两个索引合并为一个索引
--两个索引是整数
mt = {}
for i = 1, N do
for j = 1, M do
mt[(i - 1) * M + j] = 0
end
end
--索引是字符串,拼接起来,中间使用一个字符来分隔
m[s .. ":" .. t]
--其中,s和t都不能包含冒号,否则会变成 "a::b"
- 通过“稀疏矩阵”表示一个图,这个矩阵大多数元素为0或者nil,当m, n位置上有一个值x,即表示图中的节点m和n是相连的,权重(cost)为x。
- 在Lua中只需要为非nil元素付出空间
- 不能对稀疏矩阵使用长度操作符(#),因为在有效条目之间存在nil值,所以一般使用pairs对稀疏矩阵遍历,只读那些非nil的元素
function mult(a, rowindex, k)
local row = a[rowindex]
for i, v in pairs(row) do
row[i] = v * k
end
end
- table中key是无序的,所以使用pairs的迭代并不保证会按递增次序来访问元素
11.3 链表
list = nil
list = {next = list, value = v}
local l = list
while l do
<操作l.value>
l = l.next
end
Lua中很少用链表,一般都使用更简单的方式来表示数据,eg:通过一个无限大的数组来表示一个栈
11.4 队列与双向队列
List = {}
function List.new()
return {first = 0, last = -1}
end
function List.pushfirst(list, value)
local first = list.first - 1
list.first = first
list[first] = value
end
function List.pushlast(list, value)
local last = list.last + 1
list.last = last
list[last] = value
end
function List.popfirst(list)
local first = list.first
if first > list.last then error("list is empty") end
local value = list[first]
list[first] = nil
list.first = first + 1
return value
end
function List.poplast(list)
local last = list.last
if list.first > last then error("list is empty") end
local value = list[last]
list[last] = nil
list.last = last - 1
return value
end
11.5 集合与无序组
- 集合就是把集合元素作为索引放入一个table中,只需要用该值来索引table,并查看结果是否为nil
- 包,也叫“多重集合”,与普通集合不同子啊其每个元素可以出现多次。在Lua中包的表示类似于集合表示,只不过包需要将一个计数器与table的key关联,若要插入一个元素,则需要递增其计数器:
function insert(bag, element)
bag[element] = (bag[element] or 0) + 1
end
function remove(bag, element)
local count = bag[element]
bag[element] = (count and count > 1) and count - 1 or nil --不太明白
end
--只有当计数器已存在或大于0时,才保留它
11.6 字符串缓冲
--文件读取1
local buff = ""
for line in io.lines() do
buff = buff .. line .. "\n"
end
--文件读取2
local t = {}
for line in io.lines() do
t[#t + 1] = line .. "\n"
end
local s = table.concat(t)
读取1,假设每行有20个字节,已读2500行,那么buff现在就是一个50KB大小的字符串,当Lua作字符串连接buff … line … "\n"时,就创建了一个长50020字节的新字符串,并且从buff中辅助了50000字节到这个新字符串
读取2相比于读取1,将一个table作为字符串缓冲,使用table.concat,会将给定列表中的所有字符串连接起来,并返回连接的结果。
11.7 图
结点表示为对象及边表示为结点间的引用
每个结点表示为一个table,这个table有两个字段:name(结点的名称)和adj(与此结点邻接的结点集合),额外一个table来将名称对应到结点。
local function name2node(graph, name)
if not graph[name] then
graph[name] = {name = name, adj = {}}
end
reutrn graph[name]
end
- 构造一个图,逐行地读取一个文件,文件中的每行都有两个结点名称,表示在两个结点之间有一条边,边的方向从第一个结点到第二个结点。
function readgraph()
local graph = {}
for line in io.lines() do
local namefrom, nameto = string.match(line,"(%S+)%s+(%S+)")
local from = name2node(graph, namefrom)
lcoal to = name2node(graph, nameto)
from.adj[to] = true
end
return graph
end
- 使用图的算法
function findpath(curr, to, path, visited)
path = path or {}
visited = visited or {}
if visited[curr] then --判定结点是否访问过
return nil
end
visited[curr] = true --将结点标记为已访问过
path[#path + 1] = curr
if curr == to then
return path
end
for node in pairs(curr.adj) do
local p = findpath(node, to, path, visited)
if p then return p end
end
path[#path] = nil
end
- 测试
function printpath(path)
for i = 1, #path do
print(path[i].name)
end
end
g = readgraph()
a = name2node(g, "a")
b = name2node(g, "b")
p = findpath(a, b)
if p then printpath(p) end