Lua学习笔记(总结Runoob.com)
概述
- 文章是对Rounoob.com所有的Lua文章进行学习的个人总结笔记,有些是Runoob.com抄写的内容,主要是自己看来一遍,把里面的东西都实现了,然后手打了一遍知识点,进行迁移到这里,由于文章比较长,所以对其进行拆分,这是第二篇。上篇主要内容有迭代器、table、模块与包、元表、协同程序、文件I/O、错误处理和Debug、垃圾回收、面向对象
四、迭代器、table(表)、模块与包、元表(Metatable)
1、迭代器
- 是一种对象,能用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址
- 是一种支持指针类型的结构,可以遍历集合里面的每一个元素
1、泛型for迭代器
-
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
-
型 for 迭代器提供了集合的 key/value 对
for k ,v in pairs(t) do print(k,v) end --[[ for 变量列表 in 迭代函数,状态变量,控制变量 do --循环体 end --]] -- 1、调用迭代函数,把状态变量和控制变量当做参数传递给迭代函数, 状态变量只会在第一次调用的时候赋值 --2、如果迭代函数的返回值为nil,退出for循环 --如果不是nil的话,把返回值赋值给变量列表,并执行循环体 function square(state,control) if(control >= state)then else control = control +1 return control,control * control end end --遍历 for i,j in square,9,0 do print(i,j) end
-
泛型for的执行过程
- 首先,初始化,计算in后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
-
在lua中我们常常使用函数来描述迭代器,每次调用该函数,就返回集合的下一个元素,lua的迭代器包含以下两种类型
-
无状态的迭代器
- 无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
- 每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
- 这种无状态迭代器的典型的简单的例子是ipairs,它遍历数组的每一个元素。
function square(state,control) if(control >= state)then else control = control +1 return control,control * control end end --遍历 for i,j in square,9,0 do print(i,j) end
-
多状态的迭代器
- 迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。
array { "lua","Torotial"} function elementIterator(collection) local index = 0 local count = #collection --闭包函数 return function() index = index +1 if index <= count then --返回迭代器的当前元素 return collection[index] end end end for element in elementIterator(array) do print(element) end
2、table(表)
1、描述
- 一种数据结构用来帮助我们创建不同的数据类型,如数组、字典等
- 使用关联数组,可任意类型的值类作为数组的索引,但这个值不能是nil
- 是不固定大小的,根据自己的需求来进行扩容
- 通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。
2、table(表)的构造
- 当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。
3、table(表)操作
方法 | 用途 |
---|---|
table.concat (table [, sep [, start [, end]]]) | concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。 |
table.insert (table, [pos,] value) | 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾. |
table.maxn (table) | 指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了)可以自己实现。 |
table.remove (table [, pos]) | 返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。 |
table.sort (table [, comp]) | 对给定的table进行升序排序。 |
- 当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
--初始化表
mytable = {}
--指定值
mytable[1] = "lua"
--移除引用
mytable = nil
--lua垃圾回收会自动释放内存
-- 简单的table
mytable = {}
print("mytable的类型是",type(mytable))
mytable[1]="Lua"
mytable["wow"] = "修改前"
print("mytable 的索引为1的元素是",mytable[1])
print("mytable 的索引为2的元素是",mytable["wow"])
--alternatetable 和mytable是指同一个table
alternatetable = mytable
print("alternatetable 的索引为1的元素是",alternatetable[1])
print("alternatetable 的索引为2的元素是",alternatetable["wow"])
alternatetable["wow"] = "修改后"
print("mytable索引为wow的元素是",mytable["wow"])
--释放变量
alternatetable = nil
print("alternatetable是",alternatetable)
--mytable任然可以访问
print(mytable["wow"])
mytable = nil
print(mytable)
3、模块与包
1、描述
- 模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
- Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行
- 模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
2、require函数
-
用来加载模块。要加载一个模块,只需要简单地调用就可以了。
require("<模块名>") require "<模块名>" -- test_module.lua 文件 -- module 模块为上文提到到 module.lua require("module") print(module.constant) module.func3()
- 执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
4、元表(metatable)
1、描述
- 在table中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
- 元表可以允许我们改变table的行为,每个行为关联了对应的元方法。
2、两个重要函数
- setmetatable(table,metatable): 对指定table设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable会失败 。
- getmetatable(table): 返回对象的元表(metatable)。
3、__index 元方法
-
通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
-
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
-
_index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 index 返回结果。
mytabel = setmetable({key1 = "value"}{ __index = function (mytable,key) return "metatablevalue" else return nil end end }), print (mytable.key1,mytable.key2) -- mytable 表赋值为{key1 = "value"} --mytable 设置了元表,方法为__index --在mytable中查找key1,如果找到,返回该元素,找不到则继续 --在mytable中查找key2,乳沟找到则返回metatablevalue,找不到则继续 --判断元表中查看是否传入"key2"键的参数(mytable.key2已设置)如果传入“key2”参数返回metatablevalue否则返回mytable对应的键值
-
Lua查找表元素的规则如下
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回nil,有元表则继续
- 判断元表有没有__index方法,如果 ,index方法为nil,返回nil;如果index方法为一个表,则重复1,,2,3如果index方法为一个函数,则返回该函数的返回值
4、__newindex元方法
- __newindex 元方法用来对表更新,——index则用来对表访问 。
- 当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
mymetatable = {}
mytable = setmetatable({key1 = "value1"},{__newindex = mymetatable})
print(mytable.key1)
mytable.newkey = "新值2"
print(mytable.newkey,mytable.newkey)
mytable.key1 = "新值1"
print(mytable.key1,mytable.key2)
--在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。
5、为表添加操作符
模式 | 描述 |
---|---|
__add | 对应的运算符 ‘+’. |
__sub | 对应的运算符 ‘-’. |
__mul | 对应的运算符 ‘*’. |
__div | 对应的运算符 ‘/’. |
__mod | 对应的运算符 ‘%’. |
__unm | 对应的运算符 ‘-’. |
__concat | 对应的运算符 ‘…’. |
__eq | 对应的运算符 ‘==’. |
__lt | 对应的运算符 ‘<’. |
__le | 对应的运算符 ‘<=’. |
五、协同程序、文件I/O、错误处理和Debug、垃圾回收
1、协同程序
1、描述
- 拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
- 线程和协同程序区别
- 线程:一个具有多个线程的程序可以同时运行几个线程
- 协同程序:需要彼此协作运行,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
2、基本语法
方法 | 描述 |
---|---|
coroutine.create() | 创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用 |
coroutine.resume() | 重启coroutine,和create配合使用 |
coroutine.yield() | 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果 |
coroutine.status() | 查看coroutine的状态 注:coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序 |
coroutine.wrap() | 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复 |
coroutine.running() | 返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号 |
co = coroutine.create(
function(i)
print(i)
end
)
coroutine.resume(co,1) --1
print(coroutine.status(co)) -- dead
print("------------")
co = coroutine.wrap(
function(i)
print(i)
end
)
co(1)
print("---------")
co2 = coroutine.create(
function()
for i = 1, 10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) -- running
print(coroutine.running()) --therd.xxxxx
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --2
print(coroutine.status(co2)) --suspended
print(coroutine.running())
print("-----------")
2、文件I/O
1、分类
-
简单模式(simple model) 拥有一个当前输入文件和一个当前输出文件,并且提供针对 这些文件的相关操作
- 简单模式在做一些简单的文件操作时比较适合,
-
完全模式(complete model)使用外部的文件句柄来实现,它以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法
-
mode的值有
模式 描述 r 以只读方式打开文件,该文件必须存在。 w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。 a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) r+ 以可读写方式打开文件,该文件必须存在。 w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 a+ 与a类似,但此文件可读可写 b 二进制模式,如果文件是二进制文件,可以加上b + 号表示对文件既可以读也可以写
2、简单模式
-
使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
--以只读方式打开文件 file = io.open("test.lua","r") --设置默认输入文件为test.lua io.input(file) --输出文件第一行 print(io.read()) --关闭打开的文件 io.close(file) --以附加的方式打开只写文件 file = io.open("test.lua","a") --设置默认输出文件为test.lua io.output(file) --在文件最后一行添加lua注释 io.write("-- test.lua文件末尾注释") --关闭打开的文件 io.close(file)
-
在以上实例中我们使用了 io.“x” 方法,其中 io.read() 中我们没有带参数,参数可以是下表中的一个:
模式 描述 “*n” 读取一个数字并返回它。例:file.read("*n") “*a” 从当前位置读取整个文件。例:file.read("*a") “*l”(默认) 读取下一行,在文件尾 (EOF) 处返回 nil。例:file.read("*l") number 返回一个指定字符个数的字符串,或在 EOF 时返回 nil。例:file.read(5) 其他的 io 方法有:
- **io.tmpfile()?*返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
- io.type(file): 检测obj是否一个可用的文件句柄
- io.flush(): 向文件写入缓冲中的所有数据
- io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件
3、完成模式
-
通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。
-
file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:
- “set”: 从文件头开始
- “cur”: 从当前位置开始[默认]
- “end”: 从文件尾开始
- offset:默认为0
不带参数file:seek()则返回当前位置,file:seek(“set”)则定位到文件头,file:seek(“end”)则定位到文件尾并返回文件大小
-
file:flush(): 向文件写入缓冲中的所有数据
-
io.lines(optional file name): 打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。
4、注意事项
- 若使用LuaStudio,其中io.open()的默认查找路径是LuaStudio软件的根目录下,若需要open其他路径的文件,使用绝对路径即可;路径使用的时候要注意使用双斜杠。
- 简单模式下,当使用io.close()关闭文件的时候,之前设置默认输入文件和默认输出文件会失效,当你使用io.open()重新打开文件时,则需要重新设置
3、错误处理和Debug
1、错误处理
- 分类语法错误
- 语法错误通常是由于对程序的组件(如运算符、表达式)使用不当引起的。比较容易查找,一般有时候编译通过反过来查看就能找到。
- 运行错误
- 运行错误是程序可以正常执行,但是会输出报错信息
- 错误处理
- 可以通过assert和error两个函数来处理
- error
error (message [, level])
- Level=1[默认]:为调用error位置(文件+行号)
- Level=2:指出哪个调用error的函数的函数
- Level=0:不添加错误位置信息
2、Debug调试
-
函数库主要函数
debug(): 进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。
输入一行仅包含 cont 的字符串将结束这个函数, 这样调用者就可以继续向下运行。getfenv(object): 返回对象的环境变量。 gethook(optional thread): 返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数 getinfo ([thread,] f [, what]): 返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字 f 表示该函数。 数字 f 表示运行在指定线程的调用栈对应层次上的函数: 0 层表示当前函数(getinfo 自身); 1 层表示调用 getinfo 的函数 (除非是尾调用,这种情况不计入栈);等等。 如果 f 是一个比活动函数数量还大的数字, getinfo 返回 nil。 debug.getlocal ([thread,] f, local): 此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。 getmetatable(value): 把给定索引指向的值的元表压入堆栈。如果索引无效,或是这个值没有元表,函数将返回 0 并且不会向栈上压任何东西。 getregistry(): 返回注册表表,这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。 getupvalue (f, up) 此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。
以 ‘(’ (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。sethook ([thread,] hook, mask [, count]): 将一个函数作为钩子函数设入。 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义:‘c’: 每当 Lua 调用一个函数时,调用钩子; ‘r’: 每当 Lua 从一个函数内返回时,调用钩子; ‘l’: 每当 Lua 进入新的一行时,调用钩子。 setlocal ([thread,] level, local, value): 这个函数将 value 赋给 栈上第 level 层函数的第 local 个局部变量。 如果没有那个变量,函数返回 nil 。 如果 level 越界,抛出一个错误。 setmetatable (value, table): 将 value 的元表设为 table (可以是 nil)。 返回 value。 setupvalue (f, up, value): 这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字。 traceback ([thread,][message [, level]]): 如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。
4、垃圾回收
1、描述
- Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
- Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
2、垃圾回收函数
- collectgarbage(“collect”): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
- collectgarbage(“count”): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
- collectgarbage(“restart”): 重启垃圾收集器的自动运行。
- collectgarbage(“setpause”): 将 arg 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。
- collectgarbage(“setstepmul”): 返回 步进倍率 的前一个值。
- collectgarbage(“step”): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
- collectgarbage(“stop”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
六、面向对象
1、面向对象特征
- 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
2、Lua中面向对象
- 封装:对象由属性和方法组成。LUA中最基本的结构是table,所以需要用table来描述对象的属性。function可以用来表示方法。那么LUA中的类可以通过table + function模拟出来。
- 继承:可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)
- 多态:Lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量)
- 抽象:也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,
3、具体实现
-
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
r = Rectangle:new(nil,10,20)
-
我们可以使用点号(.)来访问类的属性:
print(r.length)
-
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
4、继承
- 继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。