《Lua程序设计第四版》 第三部分22~25章自做练习题答案
Lua程序设计第四版第三部分编程实操自做练习题答案,带⭐为重点。
22.1
本章开始时定义的函数getfield,由于可以接收像math?sin或string!!!gsub这样的字段而不够严谨。请将其进行重写,使得该函数只能支持点作为名称分隔符。
function getfield(f)
if string.find(f, "[^%w%._]") then
error("not '.' sep", 2)
end
local G = _G
for v in string.gmatch(f, "[%a_][%w_]*") do
G = G[v]
end
return G
end
read = getfield("io.read")
print(read)
sin = getfield("math?sin")
gsub = getfield("string!!!gsub")
22.2 ⭐
请解释下列程序做了什么,以及输出结果是什么
local foo -- 声明局部变量foo
do
local _ENV = _ENV -- 在do...end范围内生效的新环境,内部环境=外部环境
function foo() -- 局部变量foo被赋值了函数
print(X) -- 内部_ENV.X,也是外部_ENV.X
end
end
X = 13 -- 外部ENV.X 改为 13
_ENV = nil -- 外部_ENV变为nil
foo() -- 调用了局部变量foo函数,打印内部_ENV.X,因为内部_ENV依旧引用着原先的外部_ENV表,所以打印13
X = 0 -- 修改外部_ENV.X 因为外部ENV此时为nil,故发生错误
-- 13
-- 22.2.lua:11: attempt to index a nil value (upvalue '_ENV')
22.3 ⭐
请详细解释下列程序做了什么,以及输出的结果是什么
local print = print -- 局部变量print赋值了_ENV.print
function foo(_ENV, a) -- _ENV.foo 被赋值了函数,函数体根据传入的_ENV环境参数执行
print(a + b) -- a是函数定义里的形参,b是传入环境 _ENV.b
end
foo({ -- 调用 _ENV.foo
b = 14 -- 传入了一个新环境
}, 12)
foo({
b = 10 -- 传入了一个新环境
}, 1)
-- 26
-- 11
23.1 ⭐
请编写一个实验证明为什么Lua语言需要实现瞬表(记得调用函数collectgarbage来强制进行一次垃圾收集)
一个具有弱引用键和强引用值的表叫瞬表
collectgarbage() -- 清理之前dofile占用的内存空间
n = collectgarbage("count") * 1024
print(n)
local memory = {}
for i = 1, 2 ^ 10, 1 do
local t = {i}
memory[t] = function()
t[1] = t[1] + 1 -- 间接引用了键
return t
end
end
-- 假如没有瞬表
collectgarbage()
n = collectgarbage("count") * 1024
print(n)
-- 假如有瞬表了
mt = {}
mt.__mode = "k"
setmetatable(memory, mt)
collectgarbage()
n = collectgarbage("count") * 1024
print(n)
-- 再清空 memory
memory = nil
collectgarbage()
n = collectgarbage("count") * 1024
print(n)
23.2
书上23.6节的第一个示例创建了一个带有析构器的表,该析构器在执行时只是输出一条消息。如果程序没有进行过垃圾收集就退出会发生什么?如果程序调用了os.exit呢?如果程序由于出错而退出呢?
o = {
x = "hi"
}
setmetatable(o, {
__gc = function(o)
print(o.x)
end
})
o = nil
-- error("123", 123)
-- os.exit()
collectgarbage()
- 如果一个对象直到程序运行结束后还没有被回收,那么Lua语言就会在整个Lua虚拟机关闭后调用它的析构器
- 调用os.exit() 不会调用未回收对象的析构器,直接退出了程序
- 因为出错而退出程序,会在退出时调用未回收对象的析构器
23.3 ⭐
假设要实现一个记忆表,该记忆表所针对函数的参数和返回值都是字符串。由于弱引用表不把字符串当做可回收对象,因此将这个记忆表标记为弱引用并不能使得其中的键值对能够被垃圾收集。在这种情况下,你该如何实现记忆呢?
给字符串值套上一层表,变成只有一个元素的序列对象
memory = {}
setmetatable(memory, {
__mode = "v",
__len = function(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return count
end
})
collectgarbage()
function myMemory(k, v)
local tmp = memory[k]
if tmp == nil then
memory[k] = {v or k}
end
return memory[k][1]
end
myMemory("a", "a")
myMemory("b", "b")
myMemory("c", "c")
v = myMemory "b"
print(v)
print(#memory)
collectgarbage()
print(#memory)
23.4 ⭐
解释示例的程序的输出
local count = 0
local mt = {
__gc = function()
count = count - 1
end
}
local a = {}
for i = 1, 10000 do
count = count + 1
a[i] = setmetatable({}, mt)
end
collectgarbage()
print(collectgarbage("count") * 1024, count) -- 多出来的内存,10000
a = nil
collectgarbage()
print(collectgarbage("count") * 1024, count) -- 删除键的对象内存,执行值的对象的析构方法,0
collectgarbage()
print(collectgarbage("count") * 1024, count) -- 删除已被析构的值的对象内存,0
--[[
844706.0 10000
582570.0 0
22570.0 0
--]]
23.5
你需要至少一个使用很多内存的Lua脚本
- 使用不同的pause和stepmul运行脚本。看看对性能的影响
- 调整脚本,使其能够完整地控制垃圾收集器。脚本应该让垃圾收集器停止运行,然后时不时地完成垃圾收集工作。
-- collectgarbage("setpause", 0)
-- collectgarbage("setpause", 1000)
-- collectgarbage("setstepmul", 1)
-- collectgarbage("setstepmul", 1000000)
start = os.clock()
local a = {}
setmetatable(a, {
__mode = "v"
})
collectgarbage("stop")
for i = 1, 2 ^ 26, 1 do
a[i] = {}
if i % 2 ^ 12 == 0 then -- or use os.time
collectgarbage()
end
end
print(os.clock() - start)
--[[
默认 3.723
pause=0 4.404
pause=1000 3.571
stepmul=1 4.85
stepmul=1000000 3.972
默认+i % 2 ^ 13 == 0 4.385
自定义 4.559
--]]
协程传递值 ⭐
function consumer()
while true do
print(coroutine.yield())
end
end
consumer = coroutine.create(consumer)
coroutine.resume(consumer, 1) -- 第一次唤醒,为consumer传入形参,运行到yield阻塞并返回,等待一个对resume的再次调用
coroutine.resume(consumer, 2) -- 第二次唤醒,yield函数结束,返回第二次resume调用的值
coroutine.resume(consumer, 3)
--[[
2
3
]]
协程全排列
function permgen(a, n)
n = n or #a
if n <= 1 then -- 迭代终止条件(全排列的某一种结果)
-- printResult(a)
coroutine.yield(a)
else
for i = 1, n, 1 do
a[i], a[n] = a[n], a[i]
permgen(a, n - 1)
a[i], a[n] = a[n], a[i]
end
end
end
function printResult(a)
for i = 1, #a do
io.write(a[i], " ")
end
io.write("\n")
end
function permutations1(a)
local thread = coroutine.create(function() -- 需要一个函数而不是一个函数的调用
permgen(a) -- 闭包
end)
return function()
local code, res = coroutine.resume(thread)
return res
end
end
function permutations2(a)
local thread = coroutine.create(permgen)
return function()
local code, res = coroutine.resume(thread, a) -- 第一次唤醒协程传入参数a,闭包
return res
end
end
function permutations3(a)
return coroutine.wrap(function()
permgen(a)
end)
end
for v in permutations1({1, 2, 3}) do
printResult(v)
end
for v in permutations2({1, 2, 3}) do
printResult(v)
end
for v in permutations3({1, 2, 3}) do
printResult(v)
end
24.1
使用生产者驱动式设计重写生产者-消费者的示例,其中消费者是协程,而生产者是主线程
io.input("test.txt")
function producer()
while true do
local x = io.read(1)
if not x then
return
end
send(x)
end
end
function consumer(x)
while true do
io.write(x, "\n")
x = receive()
end
end
consumer = coroutine.create(consumer)
function send(x)
coroutine.resume(consumer, x)
end
function receive()
return coroutine.yield()
end
producer()
24.2 ⭐
练习6.5要求编写一个函数来输出指定数组元素的所有组合。请使用协程把该函数修改为组合的生成器。该生成器的用法如下
for c in combinations({"a","b","c"}, 2) do printResult(c) end
-- ^ 递归里不要对传入的实参做手脚,即函数形参t
function comb(list, n, res, index)
res = res or {}
index = index or 1
if #res == n then
-- io.write "{"
-- io.write(table.concat(res, ","))
-- io.write "}\n"
coroutine.yield(res)
return
end
if n - #res > #list or index > #list then
return
end
-- 选择第index个
res[#res + 1] = list[index]
comb(list, n, res, index + 1)
res[#res] = nil
-- 不选第index个
comb(list, n, res, index + 1)
end
function combinations(list, n)
c = coroutine.wrap(comb)
return function(s, c)
return c(list, n)
end, nil, nil
end
for c in combinations({1, 2, 3}, 2) do
io.write "{"
io.write(table.concat(c, ","))
io.write "}\n"
end
24.3
在示例24.5中,函数getline和putline每一次调用都会产生一个新的闭包。请使用记忆机制来避免这种资源浪费。
local lib = require 'async-lib'
function run(code)
local co = coroutine.wrap(function()
code()
lib.stop()
end)
co()
lib.runloop()
end
local putlineMemory = {}
function putline(stream, line)
local co = coroutine.running()
local callback = putlineMemory[co]
if not callback then
callback = (function()
coroutine.resume(co)
end)
putlineMemory[co] = callback
end
lib.writeline(stream, line, callback)
coroutine.yield()
end
local getlineMemory = {}
function getline(stream, line)
local co = coroutine.running()
local callback = getlineMemory[co]
if not callback then
callback = (function(l)
coroutine.resume(co, l)
end)
getlineMemory[co] = callback
end
lib.readline(stream, callback)
local line = coroutine.yield()
return line
end
run(function()
local t = {}
local inp = io.input("test.js")
local out = io.output()
while true do
local line = getline(inp)
if not line then
break
end
t[#t + 1] = line
end
for i = #t, 1, -1 do
putline(out, t[i] .. "\n")
end
end)
24.4
请为基于协程的库(示例24.5)编写一个行迭代器,以便于使用for循环来读取一个文件
local lib = require 'async-lib'
function run(code)
local co = coroutine.wrap(function()
code()
lib.stop()
end)
co()
lib.runloop()
end
local putlineMemory = {}
function putline(stream, line)
local co = coroutine.running()
local callback = putlineMemory[co]
if not callback then
callback = (function()
coroutine.resume(co)
end)
putlineMemory[co] = callback
end
lib.writeline(stream, line, callback)
coroutine.yield()
end
local getlineMemory = {}
function getline(stream, line)
local co = coroutine.running()
local callback = getlineMemory[co]
if not callback then
callback = (function(l)
coroutine.resume(co, l)
end)
getlineMemory[co] = callback
end
return function()
lib.readline(stream, callback)
local line = coroutine.yield()
return line
end, nil, nil
end
run(function()
local t = {}
local inp = io.input("test.js")
local out = io.output()
for line in getline(inp) do
t[#t + 1] = line
end
for i = #t, 1, -1 do
putline(out, t[i] .. "\n")
end
end)
24.6 ⭐ ⭐
用Lua实现一个transfer函数。如果你觉得唤醒-挂起和调用-返回类似,那么transfer就类似goto:它会挂起当前运行的协程
然后唤醒其他作为参数的协程(提示:使用某种调度机制来控制协程。这样的话,transfer会把控制权转交给调度器以通知下一个协程的运行,调度器就会唤醒下一个协程)
a协程想切换到b协程,a内部调用transfer,调用yield返回唤醒a的dispatch的resume调用,resume调用返回接收到a协程调用yield传递过来的callback,然后调用callback唤醒b协程。
local coroutines = {}
function transfer(name)
local callback = function()
return coroutine.resume(coroutines[name])
end
coroutine.yield(callback)
end
-- 注册三个线程
coroutines["print a"] = coroutine.create(function()
while true do
print("a")
transfer("print b")
end
end)
coroutines["print b"] = coroutine.create(function()
while true do
print("b")
transfer("print c")
end
end)
coroutines["print c"] = coroutine.create(function()
while true do
print("c")
transfer("print a")
end
end)
-- 调度器
dispatch = coroutine.create(function()
local callback = nil
while true do
if callback == nil then
_, callback = coroutine.resume(coroutines["print a"]) -- 起始线程
end
_, callback = callback()
end
end)
coroutine.resume(main)
25.1
改进getvarvalue,使之能处理不同的协程
function co_getvarvalue(co, name, level, isenv)
if type(co) ~= "thread" then
co = coroutine.running()
end
local value
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(co, level, i)
if not n then
break
end
if n == name then
value = v
found = true
end
end
if found then
return "local", value
end
-- 寻找非局部变量
local func = debug.getinfo(co, level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return "upvalue", v
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local _, env = co_getvarvalue(co, "_ENV", level, true)
if env[name] then
return "global", env[name]
else
return "noenv"
end
end
local val1 = 1
local co = coroutine.wrap(function()
local val2 = val1
print(co_getvarvalue(nil, "val1", 1))
print(co_getvarvalue(nil, "val2", 1))
end)
co()
print(co_getvarvalue(nil, "val1", 1))
print(co_getvarvalue(nil, "val2", 1))
_ENV
print(_G._G)
do
_ENV = {
print = _G.print -- _G是外层_G的_G
}
a = 1
print(a)
do
b = 2
print(b)
do
_ENV = {
print = print -- 如果_G.print的_G是外层_ENV的_G,所以结果是nil
}
c = 3
print(c)
do
d = 4
print(4)
end
end
end
end
25.2 ⭐
请编写一个函数与getvarvalue类似的setvarvalue
function getvarvalue(name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
value = v
found = true
end
end
if found then
return "local", value
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return "upvalue", v
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local _, env = getvarvalue("_ENV", level, true)
if env[name] ~= nil then
return "global", env[name]
else
return "noenv"
end
end
function setvarvalue(name, value, level, isenv)
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
debug.setlocal(level, i, value)
found = true
end
end
if found then
return "local change\t" .. name .. "=>" .. value
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
debug.setupvalue(func, i, value)
return "upvalue change\t" .. name .. "=>" .. value
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local _, env = getvarvalue("_ENV", level, true)
if env[name] then
env[name] = value
return "global change\t" .. name .. "=>" .. value
else
return "noenv"
end
end
a = "xx"
-- local a = 4;
print(getvarvalue("a"))
setvarvalue("a", 8)
print(getvarvalue("a"))
25.3
请编写函数getvarvalue的另一个版本,该函数返回一个包括调用函数可见的所有变量的表(返回的表中不应该包括环境中的变量,而应该从原来的环境中继承这些变量)
function getvarvalue(level, isenv)
local res = {}
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
res[n] = v
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == "_ENV" then -- 发现环境变量上值
print("发现_ENV")
mt = {}
mt.__index = _ENV -- 继承
setmetatable(res, mt)
goto continue
end
res[n] = v
::continue::
end
return res
end
a = "1"
b = "2"
local c = 3
function test()
local d = 4
local e = c -- c是闭包用到的上值
res = getvarvalue()
for k, v in pairs(res) do
print(k, v)
end
print("从继承的_ENV中寻找b", res["b"])
end
test()
25.4
请编写一个函数debug.debug的改进版,该函数再调用debug.debug的函数的词法定界中运行指定的命令(提示:在一个空环境中运行命令,并使用__index元方法让函数getvarvalue进行所有的变量访问)
a = {1, 2, 3}
debug.debug() -- 原版debug访问并修改了debug函数外的变量
print(#a)
--[[
> dofile 'test9.lua'
lua_debug> a = {1,2}
lua_debug> cont
2
]]
把 load 每次编译的代码理解为一次 do end 那么 local 也就只在当前代码段有作用,需要保存的变量要以非局部变量的方式保存到环境中。
function getvarvalue(name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
value = v
found = true
end
end
if found then
return value
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return v
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local env = getvarvalue("_ENV", level, true)
if env[name] ~= nil then
return env[name]
else
return "noenv"
end
end
function mydebug()
myENV = {
print = print
}
mt = {}
mt.__index = function(table, name)
return getvarvalue(name, 1) -- 会截取第一个参数
end
setmetatable(myENV, mt)
while true do
io.write("debug > ")
line = io.read()
if line == "cont" then
break
end
assert(load(line, nil, "bt", myENV))()
end
end
hello = "1234"
mydebug()
--[[
> dofile '25.4.lua'
debug > print(hello)
1234
]]
想保留值的类型提示,可以改成返回表。
function getvarvalue(name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
value = v
found = true
end
end
if found then
return {"local", value}
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return {"upvalue", v}
end
end
if isenv then
return {"noenv", nil}
end
-- 寻找环境变量
local env = getvarvalue("_ENV", level, true)[2]
if env[name] ~= nil then
return {"global", env[name]}
else
return {"noenv", nil}
end
end
function mydebug()
myENV = {
print = print
}
mt = {}
mt.__index = function(table, name)
return getvarvalue(name, 2)[2]
end
setmetatable(myENV, mt)
print(myENV.hello)
end
hello = "1234"
mydebug()
25.5 ⭐
改进上例,使之也能处理更新操作
function getvarvalue(name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
value = v
found = true
end
end
if found then
return value
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return v
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local env = getvarvalue("_ENV", level, true)
if env[name] ~= nil then
return env[name]
else
return "noenv"
end
end
function setvarvalue(name, value, level, isenv)
local found = false
level = (level or 1) + 1
-- 寻找局部变量
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then
break
end
if n == name then
debug.setlocal(level, i, value)
found = true
end
end
if found then
return "local change\t" .. name .. "=>" .. value
end
-- 寻找非局部变量
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
debug.setupvalue(func, i, value)
return "upvalue change\t" .. name .. "=>" .. value
end
end
if isenv then
return "noenv"
end
-- 寻找环境变量
local env = getvarvalue("_ENV", level, true)
if env[name] then
env[name] = value
return "global change\t" .. name .. "=>" .. value
else
return "noenv"
end
end
function mydebug()
myENV = {
print = print
}
mt = {}
mt.__index = function(table, name)
return getvarvalue(name, 2) -- 会截取第一个参数
end
mt.__newindex = function(table, name, value)
setvarvalue(name, value, 2)
end
setmetatable(myENV, mt)
while true do
io.write("debug > ")
line = io.read()
if line == "cont" then
break
end
assert(load(line, nil, "bt", myENV))()
end
end
hello = "1234"
mydebug()
--[[
> dofile '25.5.lua'
debug > print(hello)
1234
debug > hello = "2345"
debug > print(hello)
2345
]]
25.6
实现25.3节中开发的基本性能调优工具中的一些建议的改进
local Counters = {}
local Names = {}
local function hook()
local f = debug.getinfo(2, "f").func
local count = Counters[f]
if count == nil then
Counters[f] = 1
Names[f] = debug.getinfo(2, "Sn")
else
Counters[f] = count + 1
end
end
-- 排序
a = function()
print(1)
end
b = function()
print(1)
end
c = function()
print(1)
end
Counters[a] = 300
Counters[b] = 100
Counters[c] = 200
Names[a] = "print a"
Names[b] = "print b"
Names[c] = "print c"
s = {}
for f, name in pairs(Names) do
s[#s + 1] = f
end
table.sort(s, function(a, b)
return Counters[a] > Counters[b]
end)
for _, f in ipairs(s) do
print(Names[f])
end
25.8
示例25.6中沙盒的问题之一在于沙盒中的代码不能调用其自身的函数。请问如何纠正这个问题。
思路很简单,沙盒提供一个函数用于注册自己创建的函数,运行自己创建的函数也需要在沙盒环境的限制下运行