Lua学习笔记
类型
Lua是动态类型的语言,你不需要显示定义类型。有8种基本类型:nil,boolean ,number, string,userdata,function,thread,和 table
nil 和 false 在条件语句表示不满足,任何其它的类型值都是真。对数的所有操作是和c语言的基本操作是一样的。字符串表示不可改变的字节序列。在lua中字符串可以包含8位的值。
print(type("hello world"))
print(type(10.4*3)) --> number
print(type(print)) -->function
print(type(nil)) -->nil
print(type(type(X))) -->string
- 变量没有预先定义类型,任何变量都可以含有任何类型的值.
- type函数用来返回值的类型。
- 函数作为一等公民,可以像变量一样赋值。
print(type(a)) -->nil 没有初始化的值为nil
a = 10
print(type(a)) -->number
Nil
Nil类型只有一个值为nil,这个值表示,没有值这个概念。全家变量用nil
作为初始值,在其被赋值之前。我们可以使用来删除一个全局变量
函数
函数在Lua中的一等公民。函数可以给变量赋值,作为函数的形参,函数的返回值。
用户数据
用户数据用于将 C中数据存储到Lua中变量。用户数据,只提供了赋值和相等性测试。用户数据类型的值是一个内存块, 有两种用户数据: 完全用户数据 ,指一块由 Lua 管理的内存对应的对象; 轻量用户数据 ,则指一个简单的 C 指针。
线程
线程类型用来表示线程的执行,它是用来实现协程。Lua支持所有平台下的协程。
表
表是一种关联的数组,也就是数组的下标不仅可以使用数字,还可以使用其它的任意类型的值
,除了nil和NAN。Table的值是以上的所有类型,除了nil,任何的键对应的值如果是nil表示,这项不在表中。表是Lua中唯一的数据结构。在表中,Lua可以使用a.name 表示a['name']。 需要特别指出的是:既然函数是一等公民,那么表的域也可以是函数。 这样,表就可以携带 方法
了。
传引用
表,函数,线程,用户自定义数据都是对象。变量实际上不包含这些值,仅仅包含这些值的引用,所以赋值,传参,函数的返回值,实际上操作的都是引用,这些操作都不包含任何的复制。
错误处理
因为Lua是嵌入的语言,所有Lua动作的发起都来自C语言,通过调用Lua库。当编译或者执行Lua的chunk发生错误的时候,控制权将返回到C代码,这时,C代码就要采取相应的措施了,是打印信息,还是怎么样,自己决定。
Lua的代码可以使用error函数显示的产生一个错误,如果想要捕捉一个错误,你应该使用pcall, 来在保护模式调用一个处理函数。
元表和元方法
每个在Lua中的值都能有一个元表,这个元表是普通的Lua表,用来定义在某种特殊操作下的行为。通过设计这个元表的一些特殊字段,你可以改变这个值的行为,比如,对于一个非数值类型,你定义__add
字段的值,就可以定义+
操作的值。
在元表中的键值来源于事件的名称,相应的值被称为元方法。在上面的例子中,事情是add,元方法就是进行加法运算的函数。你可以通过getmetatable的函数来查询元表的值。当然,你可以使用setmetatable
来改变元表的值。
表和用户自定义数据有自己的元表。所有其它类型的值,每种类型共享一个元表。比如,所有的数字类型共享一个数字元表,每个字符串类型共享字符串的一个元表。默认情况下,一个值是没有元表的。但是字符串,是有一个元表的。
元表控制了操作的清单。每种操作都是通过相应的名字来标识的。每种操作的键值是用__加上字符串名称。
协程
Lua支持协程,协程也被称之为合作的线程。在Lua中的线程表示一个独立线程的执行。不像在多线程系统的线程,协程可以通过显示的调用yield函数,来终止自己的执行。
你可以调用coroutine.create来产生一个协程。它唯一的参数就是协程的主函数。create的函数仅仅创建一个新的协程,并返回一个线程句柄给协程。它并没有执行协程。
你可以通过coroutine.resume来执行协程。当你第一次调用coroutine.resume,将使用coroutinue.create返回的线程作为第一个参数。协程开始执行线程主函数的第一行。其它传给corounti.resume的函数将传给线程的主函数。然后协程开始执行,直到终止或者yield。
迭代器,类,元表
引言
元表允许我们改变table的行为,例如,使用Metatables我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时,他会检查两个表是否有一个表有元表,并且检查这个Metatable是否有_add
域。如果找到,则调用_add
函数去计算结果。Lua中每一个表都有其元表,Lua默认创建一个不带元表的表。
总结:表之间的特殊操作,通过定义其元表来解决。
t = {}
print(getmetatable(t)) -->nil
可以使用setmetatable函数设置或者改变一个表
t1 = {}
setmetatable(t,t1)
assert(getmetatable(t) == t1)
任何一个表都可以是其他一个表的元表,一组相关的表可以共享一个元表。(同时,每个表都有自己的元表)一个表也可以是自身的元表。描述其私有行为。
算术运算的元方法
下面的例子介绍如何使用元方法
Set = {}
function Set.new(t)
local set = {}
for _, l in ipairs(t) do
set[l] == true
end
return set
end
function Set.union(a, b)
local res = Set.new()
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end
function Set.intersection(a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
function Set.tostring(set)
local s = "{"
local sep = ""
for e in pairs(set) do
s = s .. sep.. e
sep = s .. ","
end
return s.. "}"
function Set.print(s)
print(Set.tostring(s))
end
现在我们想加号运算符+来执行两个集合的并操作,我们将所有结合共享一个元表,并且为这个元表添加如何处理相加的操作。为了避免名字污染,我们将元表放到set内部。
Set.mt = {} -- 集合的元表
function Set.new(t) -- 2nd version
local set = {}
setmetable(set, Set.mt)
for _, l in ipairs(t) do
set[l] = true
end
return set
end
第二步,修改 set.new 函数,增加一行,创建表的时候同时指定对应的元方法。这样一来,set.new 创建的所有的集合都有相同的元表了:
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1)) --> table: 00672B60
print(getmetatable(s2)) --> table: 00672B60
第三步,给 metatable 增加__add 函数。
Set.mt.__add = Set.union
当 Lua 试图对两个集合相加时,将调用这个函数,以两个相加的表作为参数。 通过元方法,我们可以对两个集合进行相加:
s3 = s1 + s2
Set.print(s3) --> {1, 10, 20, 30, 50}
同样的我们可以使用相乘运算符来定义集合的交集操作
Set.mt.__mul = Set.intersection
Set.print((s1 + s2)*s1) --> {10, 20, 30, 50
ipairs和pair
ipairs 返回三个值,一个迭代器函数,一个表t和0。 所以
for i,v in ipair(t) do
body
end
将进行这样的迭代(1,t[1]),(2,t[2]), pair 将返回三个值: next函数,表t 和 nill 。
for k, v in pairs(t) do
body
end
将会迭代表所有的键值对。
关系运算的元方法
元表也允许我们使用元方法:__eq
(等于),__lt
(小于),和__le
(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的 元方法,因为 Lua 将 a ~= b 转换为 not (a == b);a > b 转换为 b < a;a >= b 转换为 b <= a
表相关的元方法
__index元方法
当我们访问一个表的不存在的域,返回结果为 nil,这是正确的。实际上,这种访问触发 lua 解释器去查找__index元方法:如果不存在,返回结果为 nil;如果存在则由__index 元方法返回结果。
具体的寻找过程:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表(操作指南),如果没有元表,返回nil,有元表则继续。
- 判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值
这个例子的原型是一种继承。假设我们想创建一些表来描述窗口。每一个表必须描述窗口的一些参数,比如:位置,大小,颜色风格等等。所有的这些参数都有默认的值,当我们想要创建窗口的时候只需要给出非默认值的参数即可创建我们需要的窗口。第一种方法是,实现一个表的构造器,对这个表内的每一个缺少域都填上默认值。第二种方法是,创建一个新的窗口去继承一个原型窗口的缺少域。首先,我们实现一个原型和一个构造函数,他们共享一个原表:
-- create a namespace
Window = {}
-- create the prototype with default values
Window.prototype = {x=0, y=0, width=100, height=100, }
-- create a metatable
Window.mt = {}
-- declare the constructor function
function Window.new (o)
setmetatable(o, Window.mt)
return o
end
现在我们定义__index元方法:
Window.mt.__index = function (table, key)
return Window.prototype[key]
end
这样一来,我们创建一个新的窗口,然后访问他缺少的域结果如下:
w = Window.new{x=10, y=20}
print(w.width) --> 100
当 Lua 发现 w 不存在域 width 时,但是有一个元表带有__index 域,Lua 使用w和 width来调用__index元方法 ,元方法则通过访问原型表(prototype)获取缺少的域的结果。
这里,总结一下Lua查找一个表元素时的规则,其实就是如下3个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回nil,有元表则继续
- 判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复三步骤。如果
__index方法
是一函数,则返回该函数的返回值,如果__index是一个表,直接访问这个表中的字段。
__newindex方法
__newindex元方法用来对表更新。当你给表的一个缺少的域赋值,解释器就会查找__newindex元方法:如果存在,则调用这个函数进行赋值操作。像__index
一样,如果元方法是一个表,解释器对指定的那个表,而不是对
有默认值的表
funciton setDefault(t,d)
local mt = {
__index = function()
return d
end
}
setmetatable(t,mt)
end
tab = {x = 10,y = 20}
print(tab.x,tab.z) -- 10, nil
setDefault(tab,0)
print(tab.x,tab.z) -- 10 0
迭代器和闭包
迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在Lua中,我们使用函数来表述迭代器,每次调用该函数就返回集合的下一个元素。
迭代器需要保证上一次成功调用的状态和下一次成功调用的状态,闭包完成了这个任务。闭包是一个内部函数,它可以访问一个或者多个外部函数的外部变量。
一个简单的迭代器
我们为list写一个简单的迭代器
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
这个例子中list_iter 是一个工厂函数,每次调用都会创建一个闭包。闭包保存内部局部变量(t,i,n),使用这个迭代器。
t = {10,20,30}
iter = list_iter(t)
while true do
local element = iter()
if element = nill then
break
end
print(element)
end
迭代器都难写易用,这不是一个问题,一般Lua编程不需要自己定义迭代函数。
范性for的语义
范性for的文法如下:
for <var-list> in <exp-list> do
<body>
end
通常exp-list 是一个以逗号分割的表达式列表,通常情况下,exp-list只有一个值,就是迭代工厂的调用。
for k,v in paris(t) do
print(k,v)
end
变量列表k,v;表达式列表pair(t),在很多情况下,变量列表也是只有一个变量,比如:
for lines in io.lines() do
io.write(line, '\n')
end
变量列表中第一个变量为控制变量,其值为nil的时候结束。
for的执行过程:
- 首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值:迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
- 将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 将迭代函数返回的值赋给变量列表
无状态的迭代器
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是 ipairs,他遍历数组的每一个元素。
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
ipairs的实现
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
定义类
Lua中的表不仅在某种意义上是一种对象,像对象一样,表也有状态(成员变量);也有与对象的值独立的本性。与对象类似:表的生命期与其由什么创建,在哪儿创建没有关系。对象他们的成员函数,表也有:
Account = {balance = 0}
function Account.withdraw(v)
Account.banlance = Account.balance - v
end
这样定义创建了一个新的函数,并且保存到Account对象withdraw域内。下面我们可以这样调用:
Account.withdraw(100.00)
这个函数就是我们所谓的方法,然而在一个函数内部使用全局变量名Account是一个不好的习惯。
- 这个函数只能够在特殊的对象中(Account)使用
- 即使对这个特殊的对象而言,这个函数存在特殊的变量(Account)中可以使用,如果我们改变对象的名字,函数withdraw将不能够工作:
a = Account;
Account = nil
a.withdraw(100.100) --Error
这种行为违背了前面的对象应该有独立的生命周期的原则,自己的理解就是每个对象都有自己的生命周期,并且自己控制自己的生命周期,跟引用自己的变量没有什么关系。
一个灵活的的方法是:在定义方法的时候带上一个额外的参数,用来表示方法作用的对象,这个方法通常是self和this
function Account.withdraw(self, v)
self.balance = self.balance -v
end
这样我们像下面定义,就不会出错
a1 = Account;
Account = nil
...
a1.withdraw(a1,100.00) --ok
使用self参数定义后,我们可以将这个函数用于多个对象
a2 = {
balance = 0
withdraw = Account.withdraw
}
Lua通过使用冒号操作符来隐藏self参数的声明.
function Account:withdraw(v)
self.balance = self.balance - v
end
调用方法如下
a:withdraw(100.100)
使用冒号的效果相当于在函数定义和函数调用的时候,加一个额外的隐藏的参数。这种方式只是提供了一种方便的语法,实际上没有提供新的内容,我们可以说使用点号语法定义函数而用冒号语法调用函数。反之亦然,只要我们正确的处理额外的参数。
Account = {
balance = 0,
withdraw = function(self,v) {
self.balance = self.balance -v
end
}
function Account:deposit(v)
self.balance = self.balance + v
end
Account.deposit(Account,200)
Account:withdraw(100.00)
现在我们对象拥有了一个标识符,一个状态和操作状态的方法。但依然缺少一个class的系统,继承和隐藏。先解决定义一个问题:我们如何才能够创建拥有多个相似行为的多个对象呢?
类
Lua中不存在类的概念,每个对象定义他自己的行为并拥有自己的形状(shape)。但是,依据原型的语言,在Lua中效仿的概念不难。
在调用不属于对象的某些操作时,最先会到原型查找这些操作。在这类语言中实现类的机制,我们创建一个对象,作为其它对象的原型即可(原型对象为类,其它对象为类的实例)。类与原型的工作机制相同,都是定义了对象的行为。
如果我们有两个对象a和b,我们想让b作为a的原型对象只需要:
setmetatable(a,{_index = b })
这样,对象a调用任何不存在的成员都会到对象b中查找。术语上,可以将b看作类,a看作对象。回到前面银行账号的例子上。为了使得新创建的对象拥有和 Account相似的行为,我们使用__index 元方法
,使新的对象继承 Account。注意一个小的优化:我们不需要创建一个额外的表作为 account 对象的 metatable;我们可以用 Account表本身作为元表:
function Account:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end
当我们创建一个新的账号并且掉用一个方法的时候,有什么发生呢?
a = Account:new{balance = 0}
a:deposit(100.00)
当我们创建这个新的账号 a 的时候,a 将 Account 作为它的元表(调用Account:new 时,self 即 Account)。当我们调用 a:deposit(100.00),我们实际上调用的是a.deposit(a,100.00)(冒号仅仅是语法上的便利)。然而,Lua 在表 a 中找不到 deposit,因此他回到 metatable 的__index 对应的表中查找,情况大致如下:
getmetatable(a).__index.deposit(a, 100.00)
也就是说,Lua 传递 a 作为 self 参数调用原始的 deposit 函数。所以,新的账号对象。从 Account 继承了 deposit 方法。使用同样的机制,可以从 Account 继承所有的域。继承机制不仅对方法有效,对表中所有的域都有效。所以,一个类不仅提供方法,也提供了他的实例的成员的默认值。记住:在我们第一个 Account 定义中,我们提供了成员 balance默认值为 0,所以,如果我们创建一个新的账号而没有提供 balance 的初始值,他将继承默认值:
b = Account:new()
print(b.balance) --> 0
当我们调用 b 的 deposit 方法时,实际等价于:
b.balance = b.balance + v
(因为 self 就是 b)。表达式 b.balance 等于 0 并且初始的存款(b.balance)被赋予b.balance。下一次我们访问这个值的时候,不会在涉及到 index metamethod,因为 b 已经存在他自己的 balance 域。
继承
假设我们有一个基类:
-- 这里面的self 是Account
Account = {balance = 0}
function Account : new(o)
o = o or {}
setmetable(o,self)
self._index = self
return o
end
function Account:deposit (v)
self.balance = self.balance +v
end
function Account:withdraw(v)
if v > self.balance then
error "insuffcient funds"
end
self.balance = self.balance -v
end
我们打算从基类中派生一个子类SpecialAccount,这个之类允许客户取款超过它的余额限制。我们创建一个空类开始.
SpecialAccount = Account:new()
到目前为止,SpecialAccount只是Account的一个实例。现在奇妙的事情发生了:
s = SpeciaAccount:new({limmit = 1000.00})
SpecialAccount 从Account继承了new方法,当new执行的时候,self参数指向的是SpecialAccount.所以s的元表是SpecialAccount,__index
也是SepacialAccount,这样s就继承了SpecailAccount。后者继承了Account
s:deposit(100.0)
Lua在s中找不到deposit域,他会在SpecialAccount中找,在SpecailAcount中找不到,会在Account中查找。使用SpecialAccount特殊之处在于,它可以重新定义父类继承的方法。
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
error 'insuffient fund'
end
self.balance =self.balance -v
end
在 Lua 中面向对象有趣的一个方面是你不需要创建一个新类去指定一个新的行为。如果仅仅一个对象需要特殊的行为,你可以直接在对象中实现,例如,如果账号 s 表示 一些特殊的客户:取款限制是他的存款的 10%,你只需要修改这个单独的账号:
function s:getLimit ()
return self.balance * 0.10
end
这样声明之后,调用s:withdraw(200)将运行SpecialAccount的withdraw方法,但是当方法调用self:getLimit时,最后的定义将被触发。
字符串支持情况
- Lua解释器对字符串的支持很有限,一个程序可以创建字符串并连接字符串,但不能截取子串,检查字符串的大小,检测字符串的内容。Lua中操作字符串的功能基本来自string库
- Strings库中的一些函数非常简单:string.len(s)返回字符串s的长度;string.rep(s,n)返回重复n次字符串;你使用string.rep("a",2^20)可以创建1M bytes。string.lower(s)将s中所有的大写字母换成小写。
- string.format 用来对字符串格式化操作,是功能强大的工具。和C语言中的函数几乎一模一样。
模式匹配的函数
在string库中功能最强大的函数是:string.find(字符串查找),string.gsub(全局字符串替换),string.gfind(全局字符串查找)
string.find基本应用是目标串内搜索匹配指定模式的串。如果找到,则返回匹配两个值,匹配串的开始索引和结束索引
s = "hello world"
i,j = string.find(s,"hello")
print(i,j) --> 1 5
print(string.sub(s,i,j)) -->hello
print(string.find(s,"world")) -->7 11
print(string.find(s,'lll')) -->nil
string.find函数的第三个参数是可选的,用来指定目标串中搜索的起始位置。
string.gsub函数有三个参数,目标函数,模式串,替换串。第四个参数是用来限制替换的范围。string.gsub的第二个返回值表示他进行替换操作的次数。
s = string.gsub("Lua is cute" , "cute" ,"great")
print(s) -->Lua is great
模式
你可以在模式串中使用字符类,字符类指的是可以匹配一个特定字符集合的任意字符的模式项。比如字符类%d 匹配任意数字。所以,你可以使用%d%d/%d%d/%d%d/%d%d%d%d
搜索dd/mm/yyyy格式的日期。
s = "Deadline is 30/05/1999,firm"
date = "%d%d/%d%d%d%d"
print(string.sub(s,string.find(s,date)))
下面的表表示Lua支持的字符类。
- . 任意字符
- %a 字母
- %c 控制字符
- %d 数字
- %l 小写字母
- %p 标点字符
- %s 空白符
- %u 大写字母
- %w 字母和数字
- %x 十六进制数字
- %z 代表 0 的字符
上面字符类的大写形式表示小写说代表的集合的补集。
在模式匹配中有一些特殊的字符,他们有特殊的意义,Lua中特殊的字符如下: ( ) . % + - * ? [ ^ $ %
号作为转义字符。%.
匹配点号。%%
匹配%。 对于Lua而言,模式串就是普通的字符串。他们和其他的字符串没有什么区别,也不会受到特殊对对待。只有在模式串用于函数的时候,%
才作为转义字符。
你可以使用方括号将字符类或者字符括起来创建自己的字符串。比如: [%w_]
将匹配字母数字和下划线。可以使用修饰符号,来增强模式的表达能力,lua中有4个:
- + 匹配前一字符 1 次或多次
- * 匹配前一字符 0 次或多次
- - 匹配前一字符 0 次或多次
- ? 匹配前一字符 0 次或 1 次
'+' 匹配一个或多个字符,总是进行最长匹配。比如模式串%a+
匹配一个或多个字母或者一个单词。-
号*
一样,都匹配一个字符的0次或多次出现。但进行的是最短匹配。某些时候,这两个用起来没什么区别,但有的时候却是截然不同。
以^
开头的模式只匹配目标串的开始部分,相似的,以$
结尾的模式只匹配目标串的结尾部分。
%b
来匹配对称字符串。。常写为 '%bxy' ,x 和 y 是任意两个不同的字符;x 作为匹配的开始,y 作为匹配的结束。比如,'%b()' 匹配以 '(' 开始,以 ')' 结束的字符串
捕获
可以使用模式串的一部分匹配目标串的一部分,将你想要捕获的模式用圆括号括起来,就指定了一个捕获。在string.find中使用capture的时候,函数会返回捕获值作为额外的结果。
pair = "name = Anna"
_,_,key,value = string.find(pair,"(%a+)%s*=%s*(%a+)")
print(key,value) --name Anna
两个字母序列都是通过圆括号起来的子模式,但他们被匹配的时候,他们就会被捕获。当匹配发生的时候,find函数中总是返回被匹配串的索引下标,上面的例子 ,我们将存储到哑变量_
中,然后返回子模式匹配捕获的部分。
date = "17/7/1990"
_,_,d,m,y = string.find(date,"(%d+)/(%d+)/(%d+)")
我们可以在模式中使用向前引用,%d
(d表示第d个捕获的拷贝)假定你想查找一个字符串中单引号或者双引号引起来的子串。你可能使用下面的模式:'["'].-["']'
但是匹配类似的字符串会出现问题:"It's all right"
。为了解决这个问题,我们可以使用向前引用。
s = [[ then he said : "it's all right"
]]
a,b,c,quoted_part = string.find(s,"([" '])(.-)%1")
print quoted_part --it's all right
print c -- "
IO库
I/O 库为文件操作提供两种模式。简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。完全模式(complete model)使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法。
简单IO模式
简单模式的所有操作都是在两个当前文件之上。I/O库将当前输入文件作为标准输入,将当前输出文件作为标准输出。这样,当我们执行io.read,就在标准输入中读取一行。我们可以使用io.input和io.outp函数来改变当前文件,例如io.input(filename)就是打开给定文件,以读模式,并将其设置为当前输入文件。接下来,所有的输入都是来源于该文件。直到我们再次使用io.input和io.output。
io.write("sin(3) = " , math.sin(3),"\n")
io.write(string.format("sin (3) = %.4f\n",math.sin(3)))
在写代码的时候,我们应该避免使用io.write(a..b..c)
,这样书写,这同io.write(a,b,c)
是一样的,但是后者避免了串连的操作,而消耗较少的资源。
read函数从当前输入文件读取串,由它的参数控制读取的内容。
*all
读取整个文件*line
读取下一行*number
从串中转化一个数值- num 读取num 个字符到串。
io.read("*line")函数返回的是当前输入文件的下一行(不包含最后的换行符),当到达最后一行的时候,返回为nil,该读取方式是read默认的方式。
--读入整个文件
t = io.read("*all")
t = string.gsub(t,...) -- do the job
io.write(t)
读入每行
local count = 1
while true do
local line = io.read()
if lines = nil then
break
end
io.write(string.format("%6d ",count),line,"\n")
count = count + 1
end
然而对整个文件中的逐行迭代,最好使用io.lines迭代器。
local lines = {}
for line in io.lines() do
table.insert(lines,line)
end
table.sort(lines)
for i , l in ipairs(lines) do
io.write(1,"\n")
end
*number
选项会从一个文件中取出一个数值,只有在该参数下read函数才会返回一个数值,而不是字符串。当需要从一个文件中读取大量的数字时,数字间的空白字符串可以显著的提高性能。该选项会跳过可识别数字之间的任意空格。如果在当前的位置找不到一个数字,则返回nil。
将数字作为read的参数
除了基本的读取方式外,还可以将数值n作为read函数的参数,在这样的情况下,read函数将尝试从输入文件中,读取n个字符。如果无法读取n个字符,返回nil,否则返回一个最多包含n个字符的字符串。
完全IO模式
为了对输入进行更加全面的控制,可以使用完全模式。完全模式的核心在于句柄,类似于C语言中的文件流(FILE *)。它模仿C语言中的fopen函数,同样打开文件需要文件名称,和打开模式的字符串。模式字符串可以是“r”,"w",或者是a
。并且字符b
可添加在后面表示二进制