《Lua程序设计》--学习8

编译,执行和错误

编译

虽然我们把Lua语言称为解释型语言(interpreted language),但Lua语言总是在运行代码前先预编译(precompile)源码为中间代码 

函数loadfile是从文件中加载Lua代码段,但它不会运行代码,而只是编译代码,然后将编译后的代码段作为一个函数返回。此外,与函数dofile不同,函数loadfile只返回错误码而不抛出异常可以认为dofile函数就是这样:

如果函数loadfile执行失败,那么函数assert会引发一个错误 

 loadfile函数从文件中加载Lua代码段,但它不会运行代码,只是编译代码,然乎将编译后的代码段作为一个函数返回

函数loadfile更灵活。在发生错误的情况中,函数loadfile会返回nil及错误信息,以允许我们按自定义的方式来处理错误

如果需要多次运行同一个文件,那么只需调用一次loadfile函数后再多次调用它的返回结果即可

函数load与函数loadfile类似,不同之处在于该函数从一个字符串或函数中读取代码段,而不是从文件中读取

 在这句代码执行后,变量f就会变成一个被调用时执行i=i+1的函数

 如果要编写一个用后即弃的dostring函数(例如加载并运行一段代码),那么我们可以直接调用函数load的返回值

load(s) ()

不过,如果代码中有语法错误,函数load就会返回nil和形如“试图调用一个nil值(attempt to call a nil value)”的错误信息。为了更清楚地展示错误信息,最好使用函数assert

assert(load(s)) ()

 函数load最典型的用法是执行外部代码(即那些来自程序本身之外的代码段)或动态生成的代码。例如,我们可能想运行用户定义的函数,由用户输入函数的代码后调用函数load对其求值。请注意,函数load期望的输入是一段程序,也就是一系列的语句。如果需要对表达式求值,那么可以在表达式前添加return,这样才能构成一条返回指定表达式值的语句

 由于函数load所返回的函数就是一个普通函数,因此可以反复对其进行调用

我们也可以使用读取函数(reader function)作为函数load的第1个参数。读取函数可以分几次返回一段程序,函数load会不断地调用读取函数直到读取函数返回nil(表示程序段结束)。作为示例,以下的调用与函数loadfile等价

f  = load(io.lines(filename,"*L"))

 代码段中可以声明局部变量

 函数load和函数loadfile从来不引发错误。当有错误发生时,它们会返回nil及错误信息

此外,这些函数没有任何副作用,它们既不改变或创建变量,也不向文件写入等。这些函数只是将程序段编译为一种中间形式,然后将结果作为匿名函数返回。一种常见的误解是认为加载一段程序也就是定义了函数,

但实际上在Lua语言中函数定义是在运行时而不是在编译时发生的一种赋值操作

 当执行f = loadfile("foo.lua")时编译foo的命令并没有定义foo,只有运行代码才会定义它

 预编译的代码

Lua语言会在运行源代码之前先对其进行预编译。Lua语言也允许我们以预编译的形式分发代码。

错误

由于Lua语言是一种经常被嵌入在应用程序中的扩展语言,所以当错误发生时并不能简单地崩溃或退出。相反,只要错误发生,Lua语言就必须提供处理错误的方式。

Lua语言会在遇到非预期的情况时引发错误。

我们也可以显式地通过调用函数error并传入一个错误信息作为参数来引发一个错误。通常,这个函数就是在代码中提示出错的合理方式

 错误处理和异常

如果要在Lua代码中处理错误,那么就应该使用函数pcall(protected call)来封装代码

假设要执行一段Lua代码并捕获(try-catch)执行中发生的所有错误,那么首先需要将这段代码封装到一个函数中,这个函数通常是一个匿名函数。之后,通过pcall来调用这个函数

 函数pcall会以一种保护模式(protected mode)来调用它的第1个参数,以便捕获该函数执行中的错误。无论是否有错误发生,函数pcall都不会引发错误。如果没有错误发生,那么pcall返回true及被调用函数(作为pcall的第1个参数传入)的所有返回值;否则,则返回false及错误对象

我们可以通过error来抛出异常(throw an exception),然后用函数pcall来捕获(catch)异常,而错误信息则用来标识错误的类型

错误信息和栈回溯

模块和包

所有的标准库都是模块。我们可以按照如下的方法使用数学库

 使用表来实现模块的显著优点之一是,让我们可以像操作普通表那样操作模块,并且能利用Lua语言的所有功能实现额外的功能

函数require

以下这些用法都是正确的:

首先,函数require在表package.loaded中检査模块是否已被加载。如果模块已经被加载,函数require就返回相应的值。因此,一旦一个模块被加载过,后续的对于同一模块的所有require调用都将返回同一个值,而不会再运行任何代码。

如果模块尚未加载,那么函数require则搜索具有指定模块名的Lua文件(搜索路径由变量package.path指定,我们会在后续对其进行讨论)。如果函数require找到了相应的文件,那么就用函数loadfile将其进行加载,结果是一个我们称之为加载器(loader)的函数(加载器就是一个被调用时加载模块的函数

如果函数require找不到指定模块名的Lua文件,那么它就搜索相应名称的C标准库。[插图](在这种情况下,搜索路径由变量package.cpath指定。)如果找到了一个C标准库,则使用底层函数package.loadlib进行加载,这个底层函数会查找名为luaopen_modname的函数。在这种情况下,加载函数就是loadlib的执行结果,也就是一个被表示为Lua函数的C语言函数luaopen_modname。

不管模块是在Lua文件还是C标准库中找到的,函数require此时都具有了用于加载它的加载函数。为了最终加载模块,函数require带着两个参数调用加载函数:模块名和加载函数所在文件的名称(大多数模块会忽略这两个参数)。如果加载函数有返回值,那么函数require会返回这个值,然后将其保存在表package.loaded中,以便于将来在加载同一模块时返回相同的值。如果加载函数没有返回值且表中的package.loaded[@rep{modname}]为空,函数require就假设模块的返回值是true。如果没有这种补偿,那么后续调用函数require时将会重复加载模块。

要强制函数require加载同一模块两次,可以先将模块从package.loaded中删除:下一次再加载这个模块时,函数require就会重新加载模块。

搜索路径

ISO C(Lua语言依赖的抽象平台)并没有目录的概念。所以,函数require使用的路径是一组模板(template),其中的每项都指定了将模块名(函数require的参数)转换为文件名的方式。更准确地说,这种路径中的每一个模板都是一个包含可选问号的文件名。对于每个模板,函数require会用模块名来替换每一个问号,然后检查结果是否存在对应的文件;如果不存在,则尝试下一个模板。路径中的模板以在大多数操作系统中很少被用于文件名的分号隔开。例如,考虑如下路径:

函数require只处理分号(作为分隔符)和问号,所有其他的部分(包括目录分隔符和文件扩展名)则由路径自己定义。

Lua语言中编写模块

在Lua语言中创建模块的最简单方法是,创建一个表并将所有需要导出的函数放入其中,最后返回这个表

子模块和包

Lua支持具有层次结构的模块名,通过点来分隔名称中的层次。例如,一个名为mod.sub的模块是模块mod的一个子模块(submodule)

从Lua语言的视角看,同一个包中的子模块没有显式的关联。加载一个模块并不会自动加载它的任何子模块。同样,加载子模块也不会自动地加载其父模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值