lua

Lua的windows环境配置

1.首先去官网下源码,然后解压出来。这里以lua5.1.5为例。https://www.lua.org/versions.html 

2.打开VS的tool目录,我用的VS2013,目录是C:Program Files (x86)Microsoft Visual Studio 12.0Common7ToolsShortcuts , 然后 开发人员命令提示

3.在打开的命令行里cd到解压的源码的目录的src下面

4.逐条输入一下命令,参考http://www.imooc.com/article/4435

cl /MD /O2 //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:Lualua-5.1.5srcsystree

5.怎么使用lfs?应该是要把E:Lualua-5.1.5srcsystreeliblua5.1下的lfs.dll 放到E:Lualua-5.1.5src下。然后就可以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

                

原文地址:https://www.cnblogs.com/i-love-kobe/p/6192618.html