输入输出
简单I/O模型
Lua 文件 I/O | 菜鸟教程 (runoob.com)
对于文件操作来说,I/O库提供了两种不同的模型。简单模型虚拟了一个当前输入流(current input stream)和一个当前输出流(current output stream),其I/O操作是通过这些流实现的。I/O库把当前输入流初始化为进程的标准输入(C语言中的stdin),将当前输出流初始化为进程的标准输出(C语言中的stdout)。因此,当执行类似于io.read()这样的语句时,就可以从标准输入中读取一行。
函数io.input和函数io.output可以用于改变当前的输入输出流。调用io.input(filename)会以只读模式打开指定文件,并将文件设置为当前输入流。之后,所有的输入都将来自该文件,除非再次调用io.input。对于输出而言,函数io.output的逻辑与之类似。如果出现错误,这两个函数都会抛出异常。如果想直接处理这些异常,则必须使用完整I/O模型。
与函数print不同,函数io.write不会在最终的输出结果中添加诸如制表符或换行符这样的额外内容。
函数io.write在将数值转换为字符串时遵循一般的转换规则;如果想要完全地控制这种转换,则应该使用函数string.format:
函数io.read可以从当前输入流中读取字符串,其参数决定了要读取的数据:
调用io.read("a")可从当前位置开始读取当前输入文件的全部内容。如果当前位置处于文件的末尾或文件为空,那么该函数返回一个空字符串。
因为Lua语言可以高效地处理长字符串,所以在Lua语言中编写过滤器(filter)的一种简单技巧就是将整个文件读取到一个字符串中,然后对字符串进行处理,最后输出结果为:
作为面向行的(line-oriented)输入的一个简单例子,以下的程序会在将当前输入复制到当前输出中的同时对每行进行编号:
函数read会从输入流中读取n个字符。如果无法读取到任何字符(处于文件末尾)则返回nil;否则,则返回一个由流中最多n个字符组成的字符串。
io.read(0)是一个特例,它常用于测试是否到达了文件末尾。如果仍然有数据可供读取,它会返回一个空字符串;否则,则返回nil。
完整I/O模型
简单I/O模型对简单的需求而言还算适用,但对于诸如同时读写多个文件等更高级的文件操作来说就不够了。对于这些文件操作,我们需要用到完整I/O模型。
可以使用函数io.open来打开一个文件,该函数仿造了C语言中的函数fopen。这个函数有两个参数,一个参数是待打开文件的文件名,另一个参数是一个模式(mode)字符串。模式字符串包括表示只读的r、表示只写的w(也可以用来删除文件中原有的内容)、表示追加的a,以及另外一个可选的表示打开二进制文件的b。函数io.open返回对应文件的流。当发生错误时,该函数会在返回nil的同时返回一条错误信息及一个系统相关的错误码:
检查错误的一种典型方法是使用函数assert:
如果函数io.open执行失败,错误信息会作为函数assert的第二个参数被传入,之后函数assert会将错误信息展示出来。
在打开文件后,可以使用方法read和write从流中读取和向流中写入。它们与函数read和write类似,但需要使用冒号运算符将它们当作流对象的方法来调用。例如,可以使用如下的代码打开一个文件并读取其中所有内容:
如果想要临时改变当前输入流,可以像这样:
注意,io.read(args)实际上是io.input():read(args)的简写,即函数read是用在当前输入流上的。同样,io.write(args)是io.output():write(args)的简写。
补充知识
局部变量和代码块
Lua语言中的变量在默认情况下是全局变量,所有的局部变量在使用前必须声明
在交互模式中,每一行代码就是一个代码段(除非不是一条完整的命令)。一旦输入示例的第二行(local i=1),Lua语言解释器就会直接运行它并在下一行开始一个新的代码段。这样,局部(local)的声明就超出了原来的作用范围。解决这个问题的一种方式是显式地声明整个代码块,即将它放入一对do–end中。一旦输入了do,命令就只会在遇到匹配的end时才结束,这样Lua语言解释器就不会单独执行每一行的命令
注意这里x的范围
好处:局部变量可以避免由于不必要的命名而造成全局变量的混乱;其次,局部变量还能避免同一程序中不同代码部分中的命名冲突;再次,访问局部变量比访问全局变量更快;最后,局部变量会随着其作用域的结束而消失,从而使得垃圾收集器能够将其释放。
如果一个声明中没有赋初值,则变量会被初始化为nil
Lua语言中的一种常见用法:
这段代码声明了一个局部变量foo,然后用全局变量foo对其赋初值(局部变量foo只有在声明之后才能被访问)。这个用法在需要提高对foo的访问速度时很有用。当其他函数改变了全局变量foo的值,而代码段又需要保留foo的原始值时,这个用法也很有用,尤其是在进行运行时动态替换(monkey patching,猴子补丁)时。即使其他代码把print动态替换成了其他函数,在local print=print语句之前的所有代码使用的还都是原先的print函数。
在需要时才声明变量可以避免漏掉初始化这个变量
控制结构
所有的控制结构语法上都有一个显式的终结符:end用于终结if、for及while结构,until用于终结repeat结构。
Lua语言将所有不是false和nil的值当作真(特别地,Lua语言将0和空字符串也当作真)。
if then else
如果要编写嵌套的if语句,可以使用elseif。它类似于在else后面紧跟一个if,但可以避免重复使用end:
while
当条件为真时while循环会重复执行其循环体。Lua语言先测试while语句的条件,若条件为假则循环结束;否则,Lua会执行循环体并不断地重复这个过程
repeat
repeat–until语句会重复执行其循环体直到条件为真时结束。由于条件测试在循环体之后执行,所以循环体至少会执行一次。
和大多数其他编程语言不同,在Lua语言中,循环体内声明的局部变量的作用域包括测试条件
数值型for
for语句有两种形式:数值型(numerical)for和泛型(generic)for。
在这种循环中,var的值从exp1变化到exp2之前的每次循环会执行something,并在每次循环结束后将步长(step)exp3增加到var上。第三个表达式exp3是可选的,若不存在,Lua语言会默认步长值为1。如果不想给循环设置上限,可以使用常量math.huge:
首先,在循环开始前,三个表达式都会运行一次;其次,控制变量是被for语句自动声明的局部变量,且其作用范围仅限于循环体内。一种典型的错误是认为控制变量在循环结束后仍然存在:
如果需要在循环结束后使用控制变量的值(通常在中断循环时),则必须将控制变量的值保存到另一个变量中:
泛型for
泛型for遍历迭代函数返回的所有值,例如我们已经在很多示例中看到过的pairs、ipairs和io.lines等。虽然泛型for看似简单,但它的功能非常强大。使用恰当的迭代器可以在保证代码可读性的情况下遍历几乎所有的数据结构。
与数值型for不同,泛型for可以使用多个变量,这些变量在每次循环时都会更新。当第一个变量变为nil时,循环终止。像数值型for一样,控制变量是循环体中的局部变量,我们也不应该在循环中改变其值。
break,return和goto
我们可以使用break语句结束循环,该语句会中断包含它的内层循环(例如for、repeat或者while);该语句不能在循环外使用。break中断后,程序会紧接着被中断的循环继续执行。
return语句用于返回函数的执行结果或简单地结束函数的运行。所有函数的最后都有一个隐含的return,因此我们不需要在每一个没有返还值的函数最后书写return语句。
goto
在Lua语言中,goto语句的语法非常传统,即保留字goto后面紧跟着标签名,标签名可以是任意有效的标识符。标签名称前后各紧跟两个冒号,形如::name::
在使用goto跳转时,Lua语言设置了一些限制条件。首先,标签遵循常见的可见性规则,因此不能直接跳转到一个代码块中的标签(因为代码块中的标签对外不可见)。其次,goto不能跳转到函数外(注意第一条规则已经排除了跳转进一个函数的可能性)。最后,goto不能跳转到局部变量的作用域。
Lua语言规范中一个很有用的细节是,局部变量的作用域终止于声明变量的代码块中的最后一个有效(non-void)语句处,标签被认为是无效(void)语句。
读者可能认为,这个goto语句跳转到了变量var的作用域内。但实际上这个continue标签出现在该代码块的最后一个有效语句后,因此goto并未跳转进入变量var的作用域内。
有趣的代码:文章来源:https://www.toymoban.com/news/detail-477130.html
文章来源地址https://www.toymoban.com/news/detail-477130.html
到了这里,关于《Lua程序设计》--学习3的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!