Lua的windows环境配置
1.首先去官网下源码,然后解压出来。这里以lua5.1.5为例。https://www.lua.org/versions.html
2.打开VS的tool目录,我用的VS2013,目录是C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts , 然后 开发人员命令提示
3.在打开的命令行里cd到解压的源码的目录的src下面
4.逐条输入一下命令,参考http://www.imooc.com/article/4435
cl /MD /O2 /c /DLUA_BUILD_AS_DLL *.c
ren lua.obj lua.o
ren luac.obj luac.o
link /DLL /IMPLIB:lua5.1.5.lib /OUT:lua5.1.5.dll *.obj
link /OUT:lua.exe lua.o lua5.1.5.lib
lib /OUT:lua5.1.5-static.lib *.obj
link /OUT:luac.exe luac.o lua5.1.5-static.lib
src目录下有了 lua.exe和luac.exe的解释器,以及 lua5.3.0.dll。
5.配置环境变量,添加刚才的src目录 ,然后就可以在cmd下使用lua命令了
安装LuaFileSystem
参考:http://blog.csdn.net/hzl877243276/article/details/38919927
装这玩意可麻烦了,要先装LuaRocks
1.下载包https://github.com/luarocks/luarocks/wiki/Download
2.双击运行install.bat。
会先找lua.exe,然后会找lua51.dll,然而我装的是lua5.1.lib,这货就找不到了,试了几次都不行,最后索性把lua5.1.5.lib改成lua5.1.lib,然后就通过了
3.添加环境变量path,我这里是C:\Program Files (x86)\LuaRocks
4.cmd下运行 luarocks install luafilesystem 然后他会自动给你装好的,装完后,命令行上会告诉你装在哪里了。我的装在E:\Lua\lua-5.1.5\src\systree
5.怎么使用lfs?应该是要把E:\Lua\lua-5.1.5\src\systree\lib\lua\5.1下的lfs.dll 放到E:\Lua\lua-5.1.5\src下。然后就可以require‘lfs’ 使用了
Lua语法注意:
for循环语法格式有do ,但是 if 语句没有do 但是有then啊。。。
只能字母下划线开头
任何值都可以表示一个条件,只有false和nil为假,其他都为真,包括0和空字符串都为真
解释器程序的几个参数
lua [选项参数] [脚本]
-i
执行完命令行参数后进入交互模式,例如 lua -i test.lua
-e
可以直接在命令中输入代码,例如 lua -e "print("hello")" //注意这里输出的不是 hello 而是 nil
-l
用来加载库文件
lua -i -e "_PROMPT='LUUUUA>>>'"
用来更改命令提示符为LUUUUA>>>。只要定义一个名为”_PROMPT"的全局变量,解释器就会用它的值作为交互模式的命令提示符。当然Ctrl D退出交互模式后再进来就变回去了
-i 和 dofile 方便调试
Table
在初始化table的时候,有几种初始化风格
player = {"kobe" , "kg" , "tim" , "dirk"}
这种情况,索引自动从1开始,player[1] = kobe
player = {a = "kobe" , b = "kg" , c = "tim"}
这种情况打印player[1]就是nil了因为这里每个元素是指定了key的,通过key来访问
player = {a = "kobe" , b = "tracy" , c = "tim" , d = "kg" , {x = 0 , y = 1}}
这种混合模式是可以通过索引来访问的,前面的三个元素有key就用key来访问,最后的那个元素是个table,没有指定key如何访问呢,通过索引,从它索引从1开始
player[1].x = 0
这个地方记住,有key就用key访问,没key就用索引
for
泛型for通过一个迭代器函数来遍历所有值
a = {"kobe" , "kg" , "shaq"} for i,v in ipairs(a) do print(v) end
kobe
kg
shaq
这里的 i 是索引 ,v是索引对应的元素值
for v in ipairs(a) do print(v) end
遍历所有key
打印结果:1 2 3
这里的a如果写成 a = {a = "kobe" , b = "kg" , c = "shaq"} 没有打印结果,不管是key还是value都没有结果,所以大概只能写成那种没有指定key的形式?
break return
break return只能是一个快的最后一条语句。比如如果没有 i 的赋值语句,没有do return end 语句 ,这个是没有问题的,因为return是最后一条语句了,但是如果下面有其他语句,就语法错误,因为不是最后一条了,这个时候就要用do return end这样显示的写出来
function foo() return --语法错误 do return end --ok i = "dfdfa" end
函数
函数是一种第一类值First-Class Value:函数与其他传统类型的值具有相同的权利。
当讨论一个函数名时,实际上是在讨论一个持有某个函数的变量。理解为函数体就是变量的值,这个变量的类型叫做函数function,变量名就是通常说的函数名。变量名只是函数体的一个引用,所以这个引用可以去引用其他的函数。比如
a = print
a("hello") -- hello
这里a就指向了print的函数体。
function foo (x) return 2*x end 就是 foo = function (x) return 2*x end 的简化形式。觉得后面这种形式更能体现上述函数名的性质。
高阶函数
接受另一个函数作为实参的函数称做高阶函数 higher-order function 但是高阶函数并没有什么特权,只是Lua强调将函数视为第一类值的一直表现,即传统类型是可以作为实参的嘛,那函数也可以的。
匿名函数
通常定义一个函数都是会给一个全局变量作为函数名来引用的,但是也有没有函数名的函数,比如上面高阶函数接受函数作为实参就是匿名函数的一个用处,这个时候不需要函数名,而是直接将函数写进参数列表中。
table.sort( network , function (a , b) return (a.name > b.name) end)
词法域
将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这个特征称做词法域
names = {"peter" , "mary" , "kobe"} grades = {peter = 3 , mary = 8 , kobe = 4} --根据年级来对名字排序 function sortbygrade(names , grades) table.sort(names , function(x , y) return grades[x] < grades[y] end) end sortbygrade(names , grades) for i , v in ipairs(names) do print(v) end
peter
kobe
mary
sort函数中的匿名函数可以访问外部函数的局部变量grades,在这个匿名函数内部,grades既不是全局变量也不是局部变量,称为一个非局部变量(non-local var)
closure
一个closure就是一个函数加上该函数所需要访问的所有“非局部变量”。
function newCounter() local i = 0 print("i = " .. i) return function() i = i + 1 return i end end
这里我理解为,就是这个return的匿名函数加上 i 就是一个closure?
f = newCounter() f() -- 1 f() -- 2 g = newCounter() g() -- 1 f() -- 3
这个例子一直觉得不太好理解,就是为什么能实现 i的递增?i并不属于 f (也就是那个匿名函数),但是 f 却是可以访问这个 i 的,因为对于f来说i是一个非局部变量,并且,值得注意的是,执行完f后,i的状态是可以保存的,感觉i就像是一个全局的变量一样是不会随着f的执行完毕而消亡掉。 这里就暂时作这样的理解。
局部函数
将函数存储到一个局部变量中,即得到了一个局部函数local function。
local f = function(<参数>) <body> end --lua还提供一种写法,且尽量用这种方式 local function f (<参数>) <body> end
尾调用 tail-call elimination
tail-call,当一个函数调用是另一个函数的最后一个动作时。因为是最后一个动作,比如函数调用出现在return语句中(好像通常也是出现在return中),这个时候原函数就执行完毕了,可以不用保存它的栈信息了。所以尾调用不消耗空间,可以无数嵌套尾调用。
记住只有出现在return语句中才算是一条尾调用。
举例:迷宫游戏,从一个状态到另一个状态
closure在迭代器中的使用
所谓迭代器,就是一个可以遍历一个集合中所有元素的机制。Java中倒是听得多。
迭代器需要在每次调用之间保持一些状态,这样才能知道它所在的位置及如何步进到下一个位置。注意这里提到了保持一些状态,之前理解closure的时候就有这个感受,所以closure可以干这个事。可以保存传进来的集合或者Lua叫table,以及开始位置,步进长度。
t = {20 , 30 , 40 , 50} function values(a) i = 0 return function() i = i + 1 return a[i] end end f = values(t) while true do element = f() if element == nil then break end print("element = " .. element) end for element in values(t) do print(element) end
element = 20
element = 30
element = 40
element = 50
20
30
40
50
在while循环中,只要调用f()就可以得到下一个元素。而使用泛型for更加方便简洁!我试了下如果不用closure,大概就的用一个全局i在记录位置?唔反正看着别扭。
一个closure结构通常涉及两个函数:closure本身和一个用于创建该closure的工厂函数。比如上面的values就是一个工厂,每次调用该函数(工厂)都产生一个closure(迭代器)。
无状态的迭代器
迭代器本身不保存任何状态,避免创建closure带来的开销,将恒定状态(一个要遍历的table,在循环中不会改变)和控制变量(当前索引值)保存在for循环中,for根据这两个值来调用迭代器,迭代器根据这两个值来迭代下一个元素。
local function iter(a , i) local i = i + 1 local v = a[i] if v then return i , v end end function nostatus(a) return iter , a , 0 --返回三个值:迭代器函数iter、恒定状态a、控制变量的初值0 end t = {"one" , "two" , "three"} for i , v in nostatus(t) do --Lua会先调用iter(a , 0),得到1,a[1]以此类推 print("t" .. i .. " = " .. v) end
复杂状态的迭代器
通常迭代器有许多状态要保存,而泛型for只提供一个恒定状态和一个控制变量用于状态的保存。一个办法是可以用closure来保存,还有一个办法就是将状态保存在一个table中,这种就叫复杂状态的迭代器。
尽可能的尝试编写无状态的迭代器,将所有状态保存在for变量中,不需要在开始循环时创建任何变量。这样做不到的话就尝试closure,closure比较优雅,而且开销比创建table来的小,其次,访问”非局部变量“也比访问table字段更快
编译运行
loadstring 总是在全局环境中编译它的字符串,所以他只操作全局变量!
i = 32 local i = 0 f = load("i = i + 1 ; print(i)") g = function () i = i + 1; print(i) end f() -- 33 g() -- 1
但是这里遇到一个问题
local i = 0 i = 32 f = load("i = i + 1 ; print(i)") g = function () i = i + 1; print(i) end f() g()
把头两行替换下位置就报错。。。不解!
协同程序coroutine
对比线程
同:一条执行序列,拥有自己独立的栈、局部变量和指令指针。同时又与其他协同程序共享全局变量和其他大部分东西
异:一个具有多线程的程序是可以同时运行几个线程,而一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且一个正在运行的协同程序只会在其显示的要求挂起suspend的时候,它的执行才会暂定
Lua用一个叫做coroutine的table来存放协同程序的函数
一个协同程序有四种状态:挂起suspend、运行running、死亡dead、正常normal
要注意的是,create一个协同程序时是suspend的,并不会自动运行,像Java线程new Thread()后还要记得 t.start()来启动线程。Lua用resume来启动或者再次启动一个协同程序
协同程序的关键在于yield()函数的使用,让一个运行running的协同程序挂起suspend。
协同程序的一个有用的机制:通过一对resume-yield来交换数据
可以这样来传递参数给协同程序的函数
--第一次调用resume,没有yield等待的时候 co = coroutine.create(function(a , b , c) print("parm = " .. a .. " , " .. b .. " , " .. c) end) coroutine.resume(co , 1 , 2 , 3) -- parm = 1 , 2 , 3
resume的返回值。第一个值为true表示没有错误,后面的值都是对应yield传入的参数
co = coroutine.create(function(x , y) coroutine.yield(x + y , x - y) end) --true 30 -10 print(coroutine.resume(co , 10 , 20))
上例中如果没有yield,返回什么呢?返回主函数要返回的值,如下面的10 ,102
co = coroutine.create(function(x , y) return 10 , 102 end) --true 10 102 print(coroutine.resume(co , 10 , 20))
生产者-消费者
--consumer-driven消费者驱动 function consumer() while true do local x = receive() --consumer不断的通过调用receive来获得生产的值 print("consumer --> " .. x) end end function receive() local state , product = coroutine.resume(producer)--唤醒producer,并通过producer的中yield参数来传递新值 print("receive state = " .. tostring(state) .. " , product = ".. product) return product end producer = coroutine.create(function()--将producer放到一个coroutine中,因为要不断启动暂停 while true do local product = io.read() send(product)--将新产生的值通过yield传参来传给consumer end end) function send(product) coroutine.yield(product) end consumer()
kobe
receive state = true , product = kobe
consumer --> kobe
tim
receive state = true , product = tim
consumer --> tim
2.22
序列化
为了已更安全的方式来引用任意的字符串,使用string库的format通过"%q"来格式化输出,这样就能正确的处理其中的双引号和换行符等特殊字符
string.format("%q" , str)
注意:关于table的构造,下面这两种构造方式是一样的,但是当作为key的k换成lua关键字的时候,比如if,a = {["if"] = "hhh"} 是合法的 ,这种形式双引号中的可以是任意字符,但是,另外第二种则必须一个合法的标识符。
(但是如果采用第一种方式用Lua关键字构造了个table,要如何取出来呢?
a = {["k"] = "hhh"} b = {k = "aaa"} print(b.k) -- aaa print(a.k) -- hhh
所以,在序列化保存table的时候,可以用[]来框住key
关于#a的使用
#用来返回一个数组或者线性表的最后一个索引值。Lua将nil作为界定数组结尾的标志,当一个数组有"空隙",即中间含有nil时,#会认为这些nil元素就是结尾标记。所以,应当避免对那些有空隙的数组使用#来获取长度。像下面的代码,第一种构造式,lua自动从1开始来分配下标,挨个存放,所以不存在间隙,用#获取到的就是table的长度。而第二种构造式,即为元素显示的指定key,用#就得到的就是0
a = {"ddd" , "da" , "wew"} print(#a) -- 3 a[2] = nil print(#a) -- 1 a = {x = 10 , y = 39} print(#a) -- 0
(pairs和ipairs的区别)那么如何遍历table呢?可以用pairs和ipairs,这里要注意两者的区别。用ipairs是遍历不到第二种构造式中的元素的,因为ipairs从1开始迭代,遇到value为nil时候停止。试下将table第一个数下标从3开始,那么用ipairs也遍历不到,因为下标为1对应的value就是nil。但是用pairs是可以的,对于两种构造式都可以遍历到,得到所有key和value。
a = {[3] = "a" , [4] = "b"} for i,v in ipairs(a) do --没有任何输出,因为下标从3开始 print(i,v) end for i,v in pairs(a) do --OK print(i,v) end
table中存放方法
可以直接在定义function的时候就"加入"到table中,用a.fun()的形式,这个时候该方法对应的key就是方法名,或者之后指定key的方式 a.f = fun 添加到table中
a = {} function a.fun() print("have fun") end for k,v in pairs(a) do print(k,v) --fun function: 0x00646ed0 end --指定key a.f = fun --f function: 0x00e71d70
虚变量
有这样一种情况,某函数有多个返回值,比如ipairs有两个返回值,但是如果我只需要第二个值,这个时候就可以在用下划线“_”来表示变量,下划线本身是可以存放变量的,合法。
a = {"aa" , "bb" , "cc"} for _,v in ipairs(a) do print(v) end
table元素连接
table.concat(tb , " , ") --将tb中元素连接,并用逗号分隔
元表(metatable)
Lua中每个值都有一套预定义的操作集合,例如,数字相加,字符串连接。但是要如何将两个table相加呢?
可以通过元表来修改一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。
举个简单的例子
a = {"hhhh"} mt = {} setmetatable(a , mt) --设置a的元表为mt b = {"kkkkk"} setmetatable(b , mt) function myAdd(t1 , t2 ) print(t1[1] , t2[1]) end mt.__add = myAdd --将元方法加入元表中,该元方法用来表述如何完成加法 c = a + b -- hhhh kkkkk
环境
Lua将所有全局变量保存在一个常规的table中,这个table称为“环境”environment。Lua将环境table自身保存在一个全局变量_G中
--可以打印出所有的全局变量 --会发现有很多常用的方法名,比如error,tostring什么的,当然还有_G for k,v in pairs(_G) do print(k,v) end
2.23
两种元方法 __index __newindex
当访问一个table中的字段时,会促使解释器去查找一个叫__index的元方法。如果没有这个元方法,那么访问结果就是nil,如果有,就由这个元方法来提供最终结果
比如,写一个User,并提供用户默认名和年龄
User = {} User.default = {name = "boyaaUser" , age = 18} --创建User的默认信息 User.mt = {} --创建User的元表 function User.new(o) setmetatable(o , User.mt) --给新产生的User都设置一个元表mt return o end User.mt.__index = function (table , key) --定义__index元方法,注意这里是个匿名function return User.default[key] --直接返回默认值 end a = User.new({}) --创建一个User print("a.name = "..a.name.." , a.age = "..a.age) --已经有默认值了
上述__index是个函数,但是Lua中,它还可以是一个table,比如将上例中__index元方法的声明改成
User.mt.__index = User.default
也能得到相同的结果。当Lua查找到__index字段的时候,发现是一个table,就会继续在这个table中查找这个key
面向对象
关于冒号的使用
冒号的作用是在一个方法定义中添加一个额外的隐藏参数,以及在一个方法调用中添加一个额外的实参。只是一种语法便利
可以用冒号定义函数也可以调用函数,记住不管是定义还是调用,参数列表会隐藏一个self
Person = {name = nil} function Person:setName(name) self.name = name print("self.name = " .. name) end a = Person a:setName("kobe") --self.name = kobe print(a.name) --kobe
在Lua中引入类
Person = {} function Person:new(o) o = o or {} setmetatable(o , self) self.__index = self return o end --默认属性值 Person.name = "kobe" Person.num = 24 Person.level = "Super Star" p = Person:new() print(p.name) --kobe
类似于下面的java代码
class Person{ private String name = "kobe"; private int num = 24; private String level = "Super Star"; private Person(){ } public static Person newInstance(){ return new Person(); } public static void main(String[] args){ Person p = Person.newInstance(); System.out.println("name = " + p.name + " , num = " + p.num + " , level = " + p.level); } }
p也可以继承到Person的new函数,比如这里修改p的名字,然后再用p来创建一个新的对象
p.name = "tracy" a = p:new() print(a.name) --tracy
继承
Account = {balance = 0} --基类 并定义一个balance成员变量 --定义几个成员方法 function Account:new(o) --构造方法 o = o or {} --创建新类o,也就是一个table setmetatable(o , self) --将Account作为o的元表 self.__index = self --这里就能实现让子类都可以继承到父类Account的字段,比如balance --在访问子类没有定义的字段的时候,Lua就会去找元表,发现元表就是Account,进而在元表中寻找__index字段 --发现是一个table,即父类,那么就根据key继续在这个table中寻找 return o end function Account:deposit(v) self.balance = self.balance + v end --通过new()这个构造方法派生一个子类 --SpecialAccount extends Account SpecialAccount = Account:new() --SpecialAccount会通过元表的方式继承到Account的new方法 s = SpecialAccount:new{limit = 100} --再通过SpecialAccount派生一个类s,并新加入一个字段limit --s在这里的元表是SpecialAccount了 s:deposit(100)--s没有该字段,然后查找元表specialAccount中的__index字段,也没有,然后继续往上面从元表Account中找 print(s.balance) --100 print(Account.balance) --0 --复写deposit function SpecialAccount:deposit( ) print("override deposit 233") end --再执行一次这条代码 s:deposit(100) --override deposit 233
多重继承
是这样一个概念:一个类可以有多个父类。
那么要如何创建这样的类呢?显然不能仅通过一个类中的方法来创建。通过search方法来在多个父类中查找key从而达到继承父类字段的效果
--用来查找父类中的字段 local function search( k , plist ) for i = 1 , #plist do local v = plist[i][k] if v then return v end end end Google = {} Google.ceo = "Larry Page" function Google:getCeo() return self.ceo end Twitter = {} Twitter.ceo = "Jack" function createClass( ... ) --参数列表传入父类 local c = {} --新类 local parents = {...} --将父类存入一个table中 setmetatable(c , {__index = function ( t , k ) --设置新类的元表,并设置__index字段 return search(k , parents) --在search方法中查找父类的字段 end}) c.__index = c function c:new(o) o = o or {} setmetatable(o , c) return o end return c end Alpha = createClass(Google , Twitter) a = Alpha:new() print(a.ceo) --Larry Page
a的ceo字段不存在,那么就会去找其元表Alpha的__index字段,即上面c的__index,发现是一个表,即父类,那么就会继续在父类中查找这个key。所以上面一定要设置c.__index = c这样才能达到继承的效果
即回溯到父类中继续查找,不然的话,就相当于查找一个元表中没有设置__index的表,那么显然,如果key不存在,就是通常默认情况下的nil。这样在发现c中也没有key的时候就会去找c的元表中的__index,发现是一个方法
这样就将查找key的任务交给search这个方法,继续往上查找父类的字段
上述代码当搜索的代价比较大的时候,每次访问必然造成性能下降,一个改进措施,将继承到的字段保存到当前类中,这样在多次访问父类字段的时候就不用多次调用search了
setmetatable(c , {__index = function ( t , k ) local v = search(k , parents) t[k] = v return v end})
这样带来的问题是,将来系统运行后,父类的字段如果改变了,那么这样的修改不会延续到子类中,相当于子类已经覆盖了该字段
2.27
洗牌作业
其实这个算法是我论文的一部分,想到可以用在这里就稍微改了下拿来用了。
origin = {} --创建原始牌 tmp = " " for i = 1 , 54 do origin[i] = i tmp = tmp..origin[i].." , " end print("before shuffle:" , tmp) --产生随机密钥a , b math.randomseed(os.time()) a = math.random(1 , 10) b = math.random(1 , 10) T = {{1 , a} , {b , a*b + 1}} --变换矩阵T T11 = T[1][1] T12 = T[1][2] T21 = T[2][1] T22 = T[2][2] newTable = {} --映射矩阵 --执行变换算法 for i = 1 , 54 do local oldx = math.floor((i-1)/8) + 1 local oldy = (i-1)%8 + 1 local newx = oldx * T11 + oldy * T12 local newy = oldx * T21 + oldy * T22 newx = newx%8 + 1 newy = newy%8 + 1 new = (newy - 1)*8 + newx --将二维坐标转成一维存放 newTable[new] = origin[i] end result = {} --最终输出 --剔除table空隙 j = 1 for i = 1 , 64 do if newTable[i] then result[j] = newTable[i] j = j + 1 end end tmp = " " for i = 1 , #result do if result[i] > 54 or result[i] < 1 then print("ERROR!!!") end tmp = tmp..result[i].." , " end print("size of result = " , #result) print("after shuffle:" , tmp)
before shuffle: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 52 , 53 , 54 ,
sizeof result = 54
after shuffle: 22 , 44 , 2 , 32 , 54 , 12 , 34 , 49 , 15 , 37 , 17 , 47 , 5 , 27 , 42 , 8 , 30 , 52 , 10 , 40 , 20 , 35 , 23 , 45 , 3 , 25 , 13 , 28 , 50 , 16 , 38 , 18 , 48 , 6 , 21 , 43 , 1 , 31 , 53 , 11 , 33 , 14 , 36 , 24 , 46 , 4 , 26 , 7 , 29 , 51 , 9 , 39 , 19 , 41 ,
弱引用table
先看这样一段代码以及对应的输出
a = {} -- setmetatable(a , {__mode = "k"}) mykey = {name = "my key"} a[mykey] = 1 mykey = nil collectgarbage() for k , v in pairs(a) do print("k = " , k.name) --my key print("v = " , v) --1 end
这里的逻辑很简单,创建了一个叫做a的table,然后创建了一个叫做mykey的table,并且加入字段name用来标识这个mykey,然后用这个table作为a的一个key,并对应value 1
重点来了,把mykey指向nil,然后强制执行垃圾回收,打印输出。
这里能够正常打印出key对应的name,以及key对应的value,即使mykey已经指向nil,原因很简单,因为mykey这个引用指向的对象已经存入a中了,那么引用之后不管指向了哪里都不会影响这个对象的实体,这个实体中保存的键值对(name , my key)依然完好无损
这里我们如果将上述代码中第二行注释去掉,就会发现没有任何输出!为什么?因为第二行代码表示将a的key部分设置为弱引用,当mykey指向nil的时候,{name = “my key”}就失去了引用,这个时候垃圾回收的时候就会将这个没有了引用的key回收掉,即引用被回收,引用所指向的对象也被回收了。
同样的方法,可以将value设置为弱引用,规则也是一样的,如果value的引用丢失了,那么连带这个对象一起被回收掉
a = {} setmetatable(a , {__mode = "v"}) myvalue = {name = "my value"} -- mykey = {name = "my key"} a["mykey"] = myvalue -- mykey = nil --如果只有mykey设置为nil,那么还是有输出,因为是value被设置为弱引用,跟key无关 myvalue = nil collectgarbage() for k , v in pairs(a) do print("k = " , k) -- 如果上面mykey是一个table,也同样是被回收而没有任何输出 print("v = " , v.name) end
同理还可以将key和value都设置为弱引用,规则一样。__mode = "kv"
Java中弱引用常用于map数据结构中引用占用内存空间较大的对象。gc立刻回收。
Lua的这个弱引用的概念看着跟Java中弱引用差不多。
关于table的默认值
第一种做法
local defaults = {} --用来存放每个table的默认值 setmetatable(defaults , {__mode = "key"}) --设置元表 --当访问table不存在的索引的时候,返回defaults表中该table对应的值 local mt = {__index = function (t) return defaults[t] end} function setDefault( t , d ) defaults[t] = d --以table为key存放其对应的默认值 setmetatable(t , mt) --每设置一个table的默认值,记得设置其元表,这样才能实现默认值设置 end a = {name = "a"} b = {name = "b"} setDefault(a , "hhh") setDefault(b , "kkk") print(a.reae) --访问不存在key,得到默认值而不是nil print(b.rewrqdfhfi)
注意到这里defaults表的key设置为弱引用,这有什么用呢???
当defaults中的key也就是上述代码中的a , b指向其他地方时候,其对应的defaus中的value也就是a表的默认值将被回收掉
在上述代码中接着加入如下代码
--key的引用指向别处 a = {} b = nil collectgarbage() for k , v in pairs(defaults) do print("k = " , k.name) --没有输出,因为defaults中的key的引用丢失了,那么value也一同回收了 print("v = " , v) end
第二种做法
使用了备忘录(memoize),这个单词的意思表明这他的作用:对函数返回值进行缓存
local metas = {} setmetatable(metas , {__mode = "v"}) function setDefault(t , d) --mt中存放每个默认值对应的元表,如果该默认值存在,就复用这个值 local mt = metas[d] if mt == nil then mt = {__index = function () return d end} metas[d] = mt end setmetatable(t , mt) end a = {} b = {} setDefault(b , 233) setDefault(a , 233) --当设置了一样的默认值的时候,a,b使用同一个元表,所以这里只会打印出一个键值对 for i , k in pairs(metas) do print("i = " , i) print("k = " , k) end --当创建新的默认值的时候,才会在metas中加入新元素 print("------") c = {} setDefault(c , "cccc") for i , k in pairs(metas) do print("i = " , i) print("k = " , k) end
区别
第一种做法是每个table设置的默认值都使用内存,直接用table作为默认值表的key来管理所有默认值
第二种做法只有设置不同默认值的时候才会需要开辟新的内存。
所以当有很多table,但是少数默认值的时候,第二种做法能节省空间。
但是书上说,很少的table,共享几个公用的默认值应该选第一种,为什么呢????
2.28
table库 sort()
sort(t)默认将t中元素从小到大排序,不过可以加入自己的规则,简单的譬如从大到排就可以这样写,第二个参数加入一个函数
t = {3 , 4 , 1 , 0 , 8 , 8} table.sort(t , function (a , b) return a > b end) for _ , k in ipairs(t) do print(k) end
8 , 8 , 4 , 3 , 1 , 0
string库
s = "do not be evil" print("---sub && gsub---") print(string.sub(s , 1 , 4)) -- do n 返回从第1个到第4个位置 print(string.sub(s , 4 , -1)) --not be evil --返回从4个开始到倒数第1个 print(string.sub(s , 4 , -2)) --not be evi --返回从4个开始到倒数第2个 print(string.gsub(s , "o" , "x")) --dx nxt be evil 2 返回将o替换为x后的字符串以及一共替换了多少次 --借助gsub来统计s中空格的数量,这里gsub中两个参数都是一样的,所以返回的字符串并没有变 --这里主要是为了得到最后那个参数,也就是替换了多少次,然后通过select来选取后面参数列表中第二个参数,也就是替换了多少次 print(select(2 , string.gsub(s , " " , " "))) print("---match && gmatch---") print(string.match(s , "%a+")) --do 返回s中与模式相匹配的那部分字串 a表示字母,a+就表示一个或多个字母,即单词 words = {} for w in string.gmatch(s , "%a+") do --返回一个函数,通过这个函数可以遍历一个字符串中所有出现指定模式的地方 words[#words + 1] = w -- 找出s中所有单词,并存到words中 end print(table.concat(words , "--->")) -- do--->not--->be--->evil
模式
像用正则表达式的感觉
--*和-的区别 --都是重复0次或多次 s = "Google : [do not be evil] ! [we will take over the world]" --匹配由[开头中间若干字符并以]结尾的字串 --用%转义[和] .表示所有字符 *表示出现0次或者多次 print(string.match(s , "%[.*%]")) --[do not be evil] ! [we will take over the world] --*会尽可能的拓展来找],找到最后一个匹配的为止 -会尽可能少的拓展,配到到一个就算了 print(string.match(s , "%[.-%]")) --[do not be evil] --要实现这个需求还可以这样做 --%b用来匹配成对的字符,后面跟一个开始字符和一个结束字符 print(string.match(s , "%b[]")) --[do not be evil]
捕获
从目标字符串中抽出匹配于该模式的内容。将模式中需要捕获的部分写到一对圆括号内
s = [[aaaa: "it's all right"!!!]] a , b = string.match(s , "([\"'])(.-)%1") --it's all right print(b) --%1 表示符合模式的第一个匹配 --上述模式表示:以引号开头,单引号或者双引号,然后接着是若干字符,接着%1就代表第一个匹配 --如果前面匹配的是双引号这里就是双引号,否则就是单引号,总之这样就和之前的配对了
替换 (string.gsub的高级用法)
之前用gsub都是直接用一个字符串来替代匹配到的字符串,这里第三个参数还可以用一个函数或table当是函数的时候
- 当是函数时,gsub每次匹配到的时候就会调用该函数,传入的参数就是捕获到的内容
- 当是table时,匹配到的字符串当作key传入查询table,并用对应的value替换
s = [[There is a slogan : "do not be evil"]] --a , b = string.match(s , "(\")(.-)%1") --当是函数时 function expand(_ , s) --传进来两个参数,第一个不需要,只要第二个 print(s) return s.." ---by Goolge" end pattern = "(\")(.-)%1" s = string.gsub(s , pattern , expand) --There is a slogan : do not be evil ---by Goolge print(s) --当是table时 pattern = "slogan" t = {} t.slogan = "Google" s = string.gsub(s , pattern , t) print(s) --There is a Google : do not be evil ---by Goolge
I/O库
为文件操作提供两种模型:简单模型,完整模型
简单模型
write 和 print的区别
一个使用原则:在随意编写(quick and dirty)的程序中,或者为了调试为编写的代码中,提倡使用print;而在其他需要完全控制输出的地方使用write。(感觉像是wirte是print的高配版
上面提到完全控制输出,为什么这么说呢?因为write在输出时不会添加像制表符或回车这样的额外字符。print会自动调用其参数的tostring()方法,因此还能显示table,函数,nil
t = {} print(t) --table: 0x00686ea0 --io.write(t) 不能这样写,报错提示参数只能是string
完整IO模型
创建文件夹
os.execute("md testio") --会在当前目录(这里是lutjt的目录)创建一个叫testio的文件夹
括号里的命令应该是要根据系统来定的,如果是linux那么应该是mkdir
打开一个文件
io.open("test.txt" , "w") --打开一个文件,第二个参数是读写模式,w代表写
打开一个文件,第二个参数是读写模式,w代表写
如果没有该文件则会创建之
这里要注意的是,如果这个文件是存在的,那么上面这条代码就会将文件中内容清楚掉!!!打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。(我猜背后实现其实是把那个文件删除掉了重新创建了一个?
读取一个不存在的文件
r 读取一个文件,这个文件必须存在!
local f = io.open("test11.txt" , "r")--读取一个文件 if not f then print("f == nil") --f == nil end
因为文件不存在,所以f == nil 但是系统不会报错 那怎么办?用assert
f = assert(io.open("test11.txt" , "r")) --test11.txt: No such file or directory
这样就会正常报错,并显示错误信息了
写出来
local t = f:read("*all") io.write(t) f:close()
读取全部并打印输出到控制台,关闭io流!
这样,使用下面的代码就可以将一个文件的内容复制到另一个文件了
i = assert(io.open("test.txt" , "r")) o = assert(io.open("oo.txt" , "w")) local t = i:read("*all") o:write(t) i:close() o:close()
操作系统库
日期和时间
--将当前时间(从某个时间到此时的秒数)转换成另一种表现形式 --os.date的第二个参数不写的话默认当前时间,这里显示写出来了 t = os.date("*t" , os.time()) for k , v in pairs(t) do print(k , v) end --[[ sec 22 min 44 day 1 isdst false wday 4 yday 60 year 2017 month 3 hour 14 ]]--
os.clock()返回当前CPU时间的秒数,一般可用于计算一段代码的执行时间
print(os.getenv("OS")) --Windows_NT 获取系统环境变量 os.execute("md a") --执行一条系统命令 这里是创建一个叫做a的文件夹
3.2
关于内存泄露
function test1() print("---run test1---") local t = {} for i = 1 , 5000 do --塞5000个空表到t中 table.insert(t , {}) end end function countMem() print("---count memory---") collectgarbage("collect") local mem1 = collectgarbage("count") print("before test1 memory = " , mem1) test1() local mem2 = collectgarbage("count") print("after test1 memory = " , mem2) print("---start GC---") collectgarbage("collect") collectgarbage("collect") print("---finish GC---") local mem3 = collectgarbage("count") local difference = mem3 - mem1 print("final memory = " , mem3 , " , and difference = " , difference) end countMem()
---count memory---
before test1 memory = 25.0126953125
---run test1---
after test1 memory = 246.7529296875
---start GC---
---finish GC---
final memory = 26.4013671875 , and difference = 1.388671875
调用test1函数,然后强制GC,发现调用前后内存变化不大,说明成功的进行了回收
然后再把test1函数中的t改为全局,即去掉local修饰,再运行
---count memory---
before test1 memory = 25.03515625
---run test1---
after test1 memory = 246.80078125
---start GC---
---finish GC---
final memory = 246.73828125 , and difference = 221.703125
这里可以明显看到GC前后的内存变化很小,说明GC没有成功释放掉内存,也就是内存泄露了
debug库
该库包括两类函数:自省函数(introspective function)和钩子(hook)
自省函数:允许检查一个正在运行中的程序的各个方面。(就是会有一些包括定义行号之类的详细信息
钩子:跟踪一个程序的执行。(在遇到特定的一些事件的时候会触发钩子
function f(s) print("i am a function") end t = debug.getinfo(f) for k , v in pairs(t) do print(k , v) end --[[ linedefined 1 函数f在源码中定义的第一行的行号 currentline -1 func function: 0x00d31cf8 isvararg false namewhat lastlinedefined 3 函数f在源码中定义的第一行的行号 source @debug_main.Lua @表示函数是在一个文件中定义的 如果是通过loadstring定义的就是一个字符串 nups 0 what lua 函数类型 C 、Lua 、main nparams 0 参数个数 short_src debug_main.lua ]]--
如果函数是loadstring定义的,比如
f = loadstring(" i = i + 1 print(i)") --source i = i + 1 print(i)
模块
可以直接使用 require("model_name")来载入别的lua文件,载入的时候就直接执行那个文件了。例如,有一个hello.lua的文件,内容为
print("hello world")
那么, require("hello") 就会直接输出 hello world。但是要注意的是,只有第一次require才会执行,后面重复require一个文件是不会执行里面的代码的,比如,连续两个require("hello")只会有一次输出。
可以看下require的逻辑
function require(name) if not package.loaded[name] then end return package.loaded[name] end
在if语句块中,加载模块,如果找到lua文件就loadfile来加载,
如果是C代码就用loadlib来加载,但是这都只是加载,并没有运行
以模块名来调用运行这些代码
如果代码有返回值,则存到package.loaded中
如果没有返回值,就存放true到package.loaded中
所以如果重复require的话,package.loaded[name] = true,
这个if语句中的代码不会执行,也就不会重复执行代码。
因此如果要如果在requir后,手动将package.loaded[name] = false,那么就会重复执行一遍代码了。例如
require("hello") package.loaded["hello"] = nil require("hello")
会输出两次hello world
当然啦,如果有个需求是每次加载进来都要执行一边(虽然感觉不太可能有这种需求),可以用dofile(记得带上.lua)
如果要每次加载完了不执行,等需要的时候再执行,可以loadfile
a = loadfile("hello.lua") a()
loadfile执行后,把文件赋给一个变量a,a()的时候才真正执行
正规的搞法大概是这样的,创建一个模块最简单的方法就是:创建一个table,并将所有需要导出的函数放入其中,最后返回这个table。
--hello.lua local hello = {} function hello.fun() print("i am fun") end return hello
--test.lua a = require("hello") a.fun() -- i am fun