Torch7 系列教程之Lua语言学习教程(二)

2.Lua进阶(一)

这一章介绍和学习Lua语言中高级编程的一些技巧和方法等等,需要掌握的是迭代器、垃圾回收、编译调试以及错误处理过程等等。

2.1 迭代器与泛型for

迭代器与闭包

Lua迭代器是一种支持指针类型的结构,它可以遍历集合中的每一个元素,Lua语言中常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。
迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,即它知道来自于哪里和将要前往哪里。函数的闭包提供的机制可以很容易实现这样的一个任务。闭包是一个内部函数,它可以访问一个或者多个外部函数的外部变量,每次闭包的成功调用后这些外部变量都保存它们的值或者是状态信息,当然如果要创建一个闭包必须要创建其外部局部变量。所以典型的闭包结构包含有两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)
例如下面的一个例子:

function list_iter(t)
	local i = 0
	local n = table.getn(t)
	return function ()
		i = i + 1
		if i <= n then
			return t[i]
		end
	end
end

while循环来每次访问序列中的每一个值:

t = {10,20,30}
iter = list_iter(t)		-- 创建迭代器
while true do
	local element = iter()	-- 调用迭代器
	if element == nil then 
		break
	end
	print(element)
end

for循环来访问每一个元素的值

t = {10,20,30}
for element in list_iter(t) do
	print(element)
end

for循环中,首先调用迭代工厂;内部保留迭代函数,因此我们不需要iter变量;然后在每一个新的迭代处调用迭代器函数;当迭代器返回nil时候循环结束。
更为复杂一点的例子:迭代器遍历一个文件内的所有匹配单词。

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
-- 用for循环来读取每一个单词
for word in allwords() do
	print(word)
end

泛型for的语义
for循环在函数闭包中保存三个值:迭代函数、状态常量和控制变量。泛型for的文法如下:

for <var-list> in <exp-list> do
	<body>
end

<var-list>是一个或多个以逗号分割的变量名列表,<exp-list>是一个或多个以逗号分割的表达式列表,通常情况下 exp-list 只有一个值:迭代工厂的调用。

泛型for的执行过程说明如下:
首先,初始化计算in后面表达式的值,表达式应该返回泛型for需要的三个值:迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出的部分会被忽略
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,
状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋值给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数。
可以用下面的代码描述:

for var1,var2,...,varn in explist do
	block
end
-- 等价于以下的表达形式
do
	local _f,_s,_var = explist
	while true do
		local var1,var2,...,varn = _f(_s,_var)
		_var = var1
		if _var == nil then break end
		block
	end
end

Lua 语言中包含有两种迭代器:

  • 无状态迭代器
  • 多状态迭代器

无状态迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。Lua语言中ipairs函数就是一个无状态迭代器。
Lua函数中ipairs函数是这样实现的:

function iter(tab,index)
	index = index + 1
	local value = tab[index]
	if value then
		return index,value
	end
end
function iparis(tab)
	return iter,tab,0
end
for i,v in ipairs(tab) do
	print(i,v)
end
-- 或者是以下的遍历方式
for i,v in iter,tab,0 do
	print(i,v)
end

在很多情形下迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是函数闭包的方法,还有一种方式就是多状态迭代器,即将所有的状态信息都存放在table内,将table作为迭代器的状态常量,因此这样的迭代函数并不需要第二个参数。
举个例子,下面是利用函数闭包实现多状态迭代器:

function iter(tab)
	local index = 0
	local length = #tab
	return function ()
		index = index + 1
		if index <= length then
			return tab[index]
		end
	end
end
tab = {"one","two","three","four","five","six","seven"}
for element in iter(tab) do
	print(element)
end

2.2 编译、调试、运行

loadfile、dofile和loadstring函数
loadfile顾名思义,它只会加载文件,loadfile将文件编译代码成中间代码并且返回编译之后的chunk作为一个函数,而不执行代码,另外loadfile不会抛出错误信息而是返回错误代码。
dofile是Lua运行代码chunk的一种原始操作,它会执行代码。
loadstring与loadfile很相似,只不是它从一个字符串中读入chunk。
我们可以这样定义一个dofile函数:

function dofile(filename)
	local f = assert(loadfile(filename))
	return f()
end

如果loadfile失败assert会抛出异常错误。
loadstring可以这样使用,例如

f = loadstring("i=20;i = i + 1;print(i)")
f()		--执行函数

Lua中会把每一个chunk都作为一个匿名函数来处理。
require函数
Lua中使用require函数来加载运行库,它有以下两个基本的特点:

  1. require函数会搜索目录加载文件
  2. require会判断是否文件已经加载避免重复加载同一个文件。

require函数使用的路径是一个模式列表,每一个模式指明一种虚文件名转化成为实文件名的方法。例如,一般require函数会寻找以下路径下的文件

?;?.lua;c:\windows\?;/usr/local/lua?/?.lua

require 函数关注的只有分号以及问号,其他的信息在路径中定义。
为了确定路径,Lua 首先检查全局变量 LUA_PATH 是否为一个字符串,如果是则认
为这个串就是路径;否则 require 检查环境变量 LUA_PATH 的值,如果两个都失败 require使用固定的路径。

require 的另一个功能是避免重复加载同一个文件两次。Lua 保留一张所有已经加载
的文件的列表(使用 table 保存)。如果一个加载的文件在表中存在 require 简单的返回;表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名 require 同一个文件两次,将会加载两次该文件。
基本用法:

require"<模块名>" -- 或者是以下的方法
require("<模块名>")

Lua与C包

Lua 和 C 是很容易结合的,使用 C 为 Lua 写包。与 Lua 中写包不同,C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制
Lua 在一个叫 loadlib 的函数内提供了所有的动态连接的功能。这个函数有两个参数:
库的绝对路径和初始化函数。下面是一个调用动态链接数据库的例子:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path,"luaopen_socket")

loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用
初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在 Lua中调用它。

2.3 垃圾回收机制

Lua采用了自动内存管理,其中运行了一个垃圾收集器来手机所有的死对象来完成自动内存管理的一些工作。Lua中所有用到的内存,例如字符串、表、用户数据、函数、线程、内部结构等等。都服从自动管理。
一般情况下,Lua垃圾回收的周期分为四个阶段,即标记、整理、清扫、收尾
标记阶段,Lua将根集合中的对象标记为活跃状态,然后将任何能够通过根节点访问到的对象也标记为活跃状态。
整理阶段,Lua遍历所有的userdata,找出没有被标记且有gc元方法的userdata,并将它们标记为活跃,并放入单独的列表中。再根据所有的弱引用table删除其未被标记的key和value。
清扫阶段,Lua会遍历所有的对象,如果当前对象没有被标记,就收集它,否则清除它的标记。
收尾阶段,根据上面生成的userdata列表来调用终结函数。
Lua实现了一个增量标记-扫描收集器,并使用两个数字来控制垃圾收集循环:垃圾收集器间歇率和垃圾收集器步进倍率。
垃圾回收器函数
Lua提供了以下函数collectgarbage([opt,[,arg]])用于控制自动内存管理。

  • collectgarbage(“collect”):做一次完整的垃圾收集循环。
  • collectgarbage(“count”):以K字节数为单位返回Lua使用总内存数。
  • collectgarbage(“restart”):重启垃圾收集器的自动运行。
  • collectgarbage(“stepause”):将arg设为收集器的间歇率,返回间歇率的前一个值。
  • collectgarbage(“setstepmul”):返回步进倍率的前一个值。
  • collectgarbage(“step”):单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
  • collectgarbage(“stop”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

2.4 Lua错误处理

程序运行过程中的错误处理是必要的,在很多的地方必须要进行错误处理的操作。一般情况下错误分为两类:运行时错误和语法错误。
Lua语言中错误处理我们使用assert和error来进行错误处理。
当Lua遇到不期望的情况时就会抛出错误,它是用error函数来抛出错误的,用法如下:

error(message [,level])

功能:终止正在执行的函数,并返回message的内容作为错误信息。通常情况下,error会附加一些错误位置的信息到message头部。注意:error函数不返回任何值。
Level参数指示获得错误的位置:

  • Level = 1 [默认] :为调用error位置(文件+行号)
  • Level = 2 :指出哪个调用error函数的函数
  • Level = 0 :不添加错误位置信息

assert函数函数用法如下所示:

assert(arg [,messgae])

在实例中,assert函数首先会检查第一个参数,若没有问题,assert不做任何事情,并且返回arg参数值,否则assert函数会以第二个参数作为错误信息抛出错误。
错误处理机制
Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。
假定运行一段Lua代码,这段代码运行过程中可以捕捉所有的异常和错误。
第一步:将这段代码封装在一个函数体内部

function foo()
	...
	if unexpacted_condition then
		error()
	end
	print("The program is runnning well!")
	...
end

第二步:使用pcall调用这个函数

if pcall(foo) then
	-- 运行foo函数时候没有错误
	...
else
	-- 运行foo函数抛出一个异常,然后进行下一步的处理过程
	...
end

传递给错误信息可以是很多值,包括有字符串、表、浮点数等等,传递给error的任何信息都会被pcall捕获并且返回:

local status,err = pcall(function () error({code=121}) end) -- 匿名函数
print(err.code)  --这将会打印121

当错误发生的时候,我们常常需要更多的错误发生相关的信息,而不单单是错误发生的位置。至少期望有一个完整的显示导致错误发生的调用栈的 tracebacks,当 pcall 返回错误信息的时候他已经释放了保存错误发生情况的栈的信息。因此,如果我们想得到tracebacks 我们必须在 pcall 返回以前获取。Lua 提供了 xpcall 来实现这个功能,xpcall接受两个参数:调用函数和错误处理函数。当错误发生时。Lua 会在栈释放以前调用错误处理函数,因此可以使用 debug 库收集错误相关的信息。debug库提供了两个处理的函数:

  • debug.debug:提供一个Lua提示符,让用户来检查错误的原因
  • debug.traceback:根据调用桟来构建一个扩展的错误消息

例如下面是一个debug例子:

function test_func(number)
	print(number)
	error('error..')
end
function handle_func()
	print(debug.traceback())
end
xpcall(test_func,handle_func, 33)

小结

本章节主要学习了Lua语言中的迭代器、错误处理、垃圾回收和文件编译的知识,在接下来的一些项目中,会经常用到这里面的一些知识问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值