zoukankan      html  css  js  c++  java
  • 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

                    

  • 相关阅读:
    提交订单的时候,实现拦截,登录成功后跳转回之前的页面;使用redis生成指定起始位置的id
    iOS Outlets Referencing Outlets
    iOS 常用控件 参数
    iOS UIImageView设置为圆形
    iOS Xcode个人常用插件
    iOS Xcode注释的几种使用方法
    MFC MSBDutyTable下载地址
    MFC HTTP访问URL
    MFC 程序以管理员权限运行
    MFC 打开文件夹选择框并获取文件夹路径
  • 原文地址:https://www.cnblogs.com/i-love-kobe/p/6192618.html
Copyright © 2011-2022 走看看