Lua学习笔记1

关于Lua
  1993年由巴西里约热内卢天主教大学计算机系Roberto Ierusalimschy(莱鲁萨利姆斯奇)等人编写
  一开始就没有准备去实现C语言已经实现的很是出色的方面
  与C语言之间实现很是好的交互能力,一门胶水语言
  很是适合C程序号学习,互补性很是强
  自动内存管理机制(垃圾回收器),优秀的字符串处理能力,动态大小数据的处理能力   
  简易性:轻量级,小巧简单易学,概念很少
  高效:能够说是目前最快的脚本语言,运行效率很是高,占用内容不多,源代码量很是小
  可移植性:没有使用任何条件编译处理不一样平台,纯粹的ANSI C编写完成,
            任何支持ANSI C的平台都能编译经过   
  三种开发形式
        1.独立使用lua实现功能
        2.使用C语言为主,lua为扩展的方式
        3.使用lua为主,C语言为扩展的方式
开始
  程序块chunk
    交互模式里输入的一行代
    一个文件里的代码
    也就是一连串的语句或命令
    连续的lua语句之间不须要分隔符,但也可使用分号,若是你愿意的话
    在交互模式中输入的每条命令都会当即执行
    使用-i能够在运行代码块后进入交互模式
    使用dofile能够加载代码文件进行调试
  词法规则
    大小写敏感
    任何字母数字和下划线,但不能是数字开头
    尽可能不要使用以单下划线开头后跟一个或多个大写字母的变量,这些大多用于系统保留了
    “_"是哑变量(Dummy Variable)
    and break do else elseif end false for function
    if in local nil not or repeat return then true until while
    "--"为单行注释
    --[[   --]]为多行注释
    使用---[[能够很方但的取消大块注释
  全局变量
    在任何在方对一个变量赋值就声明一个全局变量
    直接访问未声明的全局变量不会产生错误,会返回nil
    要删除一个全局变量能够给它赋值nil,一般是没有必要这么作的
  局部变量
    local声明
    仅在声明的做用域中有效,如do end
    尽可能使用local来声明变量
 
类型与值
  lua是一种动态类型的语言,在语言中没有类型定义的语法,每一个值都携带了它自身的类型信息
  lua中有8种基础类型
    nil
      只与自身相等assert(nil==nil),空值
    boolean
      true
      false
      nil和false是假,其它都是真,0和""都是true
    number
      浮点实数,没有使用整数
    string
      8位编码也就是个char
      能够包含任何字符,包括\0,很象stl里的string类,能够当二进制流看待
      string.len和#返回的是实现长度,不以\0为结束标记
      不能够修改字符串变量内容,只能从新赋值
      lua能高效的处理大字符串,不用怀疑lua字符串的处理能力
      特殊字符与c同样,如换行是\n tab是\t
      多行字符串定义[[ ]]
      \<ddd>一至三个数据表示字符,ASCII码
      在字符串上使用数学运算会先前字符串转换成数字
      ".."是字符串链接操做符"wang".."ning"
      在数字后使用字符串链接操做符".."时,要有一个空格,否则会认为是小数点
      尽可能不要依赖lua的自动类型转换,可使用tostring,tonumber手动转
    userdata
      一块由c向lua申请的内存,存在lua里,申请者自行解析
    function
      lua中的函数是做为”第一类值“,能够随意赋给一个变量
    thread
      协同线程lua没有真正的多线程,他都是串行执行的,生产者消费者模式
      只能本身中止本身
      可使用守护线程来在多个协同线程中经过状态判断来模拟多线程的环境
    table
      lua里惟一的一种数据结构
      lua里的module package object都是用table实现的
      table是一个对象,全部对他的
      能够表示出多种数据结构
        数组
          能够不写key添加数据如:t={1,2,3,4}
          自动定义从下标1开始顺序向后加一
          全部没有显示声明key的项都是自动生成数组下标的
          lua里与c不同的地方就是一标都人1开始,必定记住
          t={"a", 2="b", "c", 4=1, 2, 6=3}
          t[1] = "a"
          t[2] = "c"
          t[3] = 2
          t["2"] = "b"
          t["4"] = 1
          t["6"] = 3
          后面三项再也不是数组而是hash表
          使用#能够获得数组长度,但要求全部下标是从1开始连续的
          t={}
          t[1] = 1
          t[100] = 100
          的长度是1,由于它以找到nil的值为结束,以上的定义方式数组断了
          二维数组就是table套table
          t={ {100,200}, {300,400}}
          t[1][1] = 100
          t[1][2] = 200
          t[2][1] = 300
          t[2][2] = 400
        链表
          t1 = {p=t3, v="value", n=t2}
          t2 = {p=t1, v="value", n=t3}
          t3 = {p=t2, v="value", n=t1}
        环形链表
        hash表
        队列
        栈
      可使用lua中的任何类型做key和value,除nil不能当key,当value是删除
      只能显示的声明一个table,t = {}
      添加数据
        t = {"a","b","c",1,2,3}
        t={1="a",2="b",3="c",4=1,4=2,6=3}
        以上两人个定义不相等,显示声明时字符串类型的key能够不写双引号
        t[1] = "a"
        t[2] = "b"
        t[3] = "c"
        t[4] = 1
        t[5] = 2
        t[6] = 3
        这个与第一个相同
        t["1"] = "a"
        t["2"] = "b"
        t["3"] = "c"
        t["4"] = 1
        t["5"] = 2
        t["6"] = 3
        这个与第二个相同
        t={x="lua"}
        t={} t.x="lua" t["x"] = "lua"
  使用type函数能够返回一个能够显示的字符串
 
表达式
  算术操做符
    +(加法) -(减法) *(乘法) /(除法) ^(指数) %(取模) -(负号)
    x%1的结果是x的小数部分,x-x%1是整数部分
  关系操做符
    < > <= >= == ~=
    的有操做符的运算结果都是true或false
    nil仅与nil相等
    不等于与c不同,不是!=
    a={} a.x=1 a.y=0
    b={} b.x=1 b.y=0 
    c = a 
    assert( a==c ) 
    assert( a~=b ) 
    table userdata function仅作引用比较,引用的是一个就同样
  逻辑操做符
    and
      a and b
      第一个为false返回第一个值,否则返回第二个值
    or
      a or b
      第一个为true返回第一个值,否则返回第二个值
      (a and b) or c 至关于c中的a?b:c 但b不为假,太抽象慎用
    not
      assert(not "wangning" == false)
      assert(not nil == true)
      只会返回true或false
  优先级
    ^
    not # -(负号)
    * / %
    + -
    ..
    < > <= >= ~= ==
    and
    or
语句
    赋值
        多重赋值
        a, b, c, d = 1, 2, 3, 4
        a, b, c = 1, 2
        assert(c == nil)
    控制结构
        if then elseif else end
        while end
        repeat until
        for循环
            数字型for
                for var=exp1,exp2,exp3 do <执行体> end
                exp3能够不填,默认1
            泛型for
                for k,v in ipairs(t) do <执行体> end
                使用迭代器遍历
        break和return
            break用于结束循环
            return用于结束函数,后跟返回结果
            任何一个函数的结尾处都有一句隐式的return,因此没有返回值的函数能够不用显示写出return
            break和return必须是一个块的最后一条语句或是end else until的前一条语句
            若是确须要在内容中有return或break可使用do end来控制
函数
  定义
    function mytest(a,b,c) <函数体> end
    mytest = function(a,b,c) <函数体> end
    local function mytest(a,b,c) <函数体> end
    local mytest = function(a,b,c) <函数体> end
    t = {} t.mytest = function(a,b,c) <函数体> end
    t = { mytest = function(a,b,c) <函数体> end}
    t = {} function t.mytest(a,c,b) <函数体> end
 
  调用
    基本调用 
        a,b,c = mytest(1,2,3)
    冒号语法糖调用
        o.foo(o,x) = o:foo(x) 
        使用冒号调用时会把冒号前的内容看成函数的第一个参数传入
    多重返回值
        function mytest()
                return 1,2,3
        end
        a,b = mytest()
        assert(a==1 and b==2)
        a,b,c,d = mytest()
        assert(c==3 and d==nil)
        assert(select("#",mytest()==3))
    变长参数
        function mytest(...)
                a,b=...
                return a,b
        end
        a,b = mytest(1,2)
        assert(a==1 and b==2)
         "..."是表达式,表明变长参数如:return ...就是返回全部传入的参数
        {...}表明全部变长参数的集合,能够象使用table的同样访问也能够用ipairs{...}遍历
        select函数操做访问变长参数的能力
        select(n, ...)访问第n个参数
        select("#", ...)得到参数个数
    特殊调用
        当函数仅有一个参数而且是字符串时可使用 mytest "wangning"方式,不用写()
        当函数仅有一个参数而且是table时可使用 mytest {a=1, b=2, c=3}的方式,不用写()
    具名实参
        function showwindow(opt)
                print(opt.x)
                print(opt.y)
                 print(opt.title)
                print(opt.text)
        end
        showwindow {x=0, y-0, title="hi!", text="helloworld"}
 
  闭包(词法域)
    在函数内定义函数,内部函数能够访问外部函数的变量
        function mytest()
                local i = 0
                return function()
                i = i + 1
                return i
                end
        end
        my = mytest()
        print(my()) --> 1
        print(my()) --> 2
        my1 = mytest()
        print(my1()) -->1
        print(my1()) -->2
        print(my()) --> 3
    函数式编程的基础
    能够实现如迭代器之类的功能,实用性很高
    这种形式就是实现了单一功能的类的对象
 
  尾调用消除 tail-call elimination
    最后一句若是是一个函数调用就是尾调用
    至关于goto,也可用于状态机
    当前函数真正结束,不会在栈中了
    return <func>(<args>)才是真正的尾调用
    function f(x) g(x) end不是尾调用,它还会回来
错误处理
  assert(exp)
  error("error message text")
  pcall安全调用
协同程序
  lua没有真正的多线程,都是使用协同程序也实现的多线程
  lua是非对称式协同程序(semi-coroutine),它提供两个函数来处理执行权
  任一时刻只能有一个协同程序在执行
  只能本身挂起本身才会中止工做,没法从外部中止其工做
  与生产者消费者模型同样互斥协同
  全部协同程序的函数放在coroutine的table里
  co = coroutine.create(function() print("wangning") end)
  assert(type(co)=="thread")
  create的参数就是协同程序执行的内容,返回一个thread类型的变量
  4种状态:挂起(suspended)、运行(running)、死亡(dead)、正常(normal)
  刚建立的协同程序处于挂起状态,能够用status检查状态
  assert(coroutine.status(co)=="suspended")
  coroutine.resume(co)用于启动或再次启动协同程序,将状态改成running
  当协同程序执行完成任务函数后处于dead状态
  在任务函数中使用coroutine.yield()能够挂起协同程序
  而后调用coroutine.resume()再次启动已挂起协同程序
  若是协同程序已经处于dead状态时,resume会返回false失败
  当协同程序A唤醒另外一个协同程序B时,A将中止执行,换作B执行
  这时A的状态为正常状态normal
  可使用resume和yield来交换数据
  第一次调用resume的额外参数(第二个之后的参数),都将视为协同程序任务函数的参数传入
  以后都将成为yield的返回值
  resume的第一个返回值表示成功失败,第二个之后的值都是yield的传入参数
  当协同程序的任务函数执行完成后,函数的返回值会成为resume的额外返回值
  co = coroutine.create(function(a,b,c)
      print(a,b,c)
      print(coroutine.yield(4,5,6))
      return 10
  end)
  res, a,b,c = coroutine.resume(co,1,2,3) --> 1 2 3
  print(res,a,b,c) --> true 4 5 6
  res, d = coroutine.resume(co, 7,8,9) --> 7 8 9
  print(res, d) --> true 10
  print(coroutine.resume(co)) --> false cannot resume dead coroutine
  可使用一个协同程序负责主任务循环,判断其它协同程序状态,有闲置的就使其执行,实现多线程功能
 
元表与元方法
  基本概念
        1.lua中每一个值都有一个元表
        2.table和userdata能够有各自独立的元表
         3.其它类型的值共享其类型所属的单一元表
         4.lua在建立新table时不会建立元表
         5.其它基本类型的加减等操做都有预约义操做,table只能经过元表及元方法
         6.getmetatable(t)得到table的元表
        7.setmetatable(t, mt)设置table的元表为mt
        8.t={} mt={} setmetatable(t,mt) assert(getmetatable(t)==mt)
         9.任何table均可以做为任何值的元表,一组table能够共享同一个元表
        10.一个table也能够做为本身的元表
         11.在lua代码中只能设置table的元表,其它类型值的元表只能在C代码中设置
  算术类元方法
         __add(加法)对应"+"操做符
        __sub(减法)对应"-"操做符
        __mul(乘法)对应"*"操做符
        __div(除法)对应"/"操做符
        __unm(相反数) 对应一元"-"操做符
        __mod(取模)对应"%"操做符
        __pow(乘幂)对应"^"操做符
        __concat(链接)对应".."操做符
        __len(求长度)对应"#"操做符
         先找第一个值的元方法,若是没有再找第二个值的元方法,都没有报错
  关系类元方法
    __eq(等于)
    __lt(小于)
    __le(小于等于)
    没有大于和不等于元方法,但能够转化实现
           a~=b转化为not(a==b)
           a>b转化为b<a
           a>=b转化为b<=a
  库定义元方法
    __tostring(字符串转换)
         tostring函数会用此元方法进行转换
    __metatable(指向元方法)
         setmetatable、getmetatable会访问这个元方法
         若是设置成其它内容就能够起到保护元表的功能
    __mode(弱引用table模式)
        它的值是一个字符串
        若是包含"k"则表示table里的key是弱引用模式
        若是包含"v"则表示table里的value是弱引用模式
  table访问的元方法
    能够改变table行为的方法
    __index(访问表中不存在的字段)
           当没有这个元方法时访问不存在字段会返回nil
           当有元方法时两种访问形式
                 做为函数时有两个参数,第一个是被访问的table,第二个是不存在的key
                 做为table时就从这个table里找被访问的table里不存在的这个key
           一般用于实现继承特性
           做为函数的时候开销会大一些,但更灵活,能够实现多重继承和缓存等功能
           若是不想涉及元方法,可使用rawget(t,i)"原始访问",不会加速代码执行
    __newindex(给表中不存在的字段赋值)
           当没有这个元方法时会在被访问的table里建立新字段并赋值
           当有元方法时两种访问形式
                 做为函数时有三个参数,第一个是被访问的table,第二个是不存在的key,第三个是value
                 做为table时,会在这个table里赋值而不是在被访问table里赋值
           可使用rawset(t,k,v)绕过元方法赋值
    能够利用这两个元方法实现不少table的特殊功能
           1.具备默认值的table,把带有值的table做为__index元方法
           2.跟踪table的访问
               t = {} --原table
               local _t = t --私有化访问
               t = {}  --建立代码,名字相同
               mt = {}
               mt.__index = function(t,k)
                     print("access "..tostring(k))
                     return _t[k] --访问原来的table
               end
               mt.__newindex = function(t,k,v)
                     print("update "..tostring(k).." to "..tostring(v))
                     _t[k] = v --更新原来的table
               end
               setmetatable(t, mt)
               但这个例没法遍历原来的table,pairs只能操做代理table
           3.只读table,__index指向被访问table,__newindex弹错
环境
  全局变量table
         lua把全部的全局变量存在一个table里,并把这个table赋值给一个全局变量_G
         _G也在这个全局变量的table里,它就是一个普通的全局变量
         能够用这种方法遍历全部全局变量 for k, v in pairs(_G) do print(k,v) end
         可使用_G["全局变量名"]来访问全局变量,与直接访问同样效果
         能够经过对_G设置元表来控制对全局变量的访问行为
         _G只是个全局变量,对其从新赋值则它就是别的东西了,这种状况只能经过getfenv找回全局table了
  非全局的环境
         lua中每一个函数均可以有本身的环境
         lua中加载每一个程序块其实就是把这个程序块看做是一个函数来调用一下
         每一个程序块最后均可以有一行return xxx
        若是有require后返回的就是这个xxx
        因此能够想象一个程序文件被加载就是一个函数被调用了,这个文件的内容整个就是一个大函数
         由于每一个函数均可以有本身的环境,因此每一个程序块都有本身的环境
        这也正是为何会命名为setfenv、getfenv了
        setfenv(f, table)
            1.第一个参数若是是函数则改变这个函数的环境为第二个参数的table
            2.第一个参数若是是数字,则1表明当前函数,2表明调用本身的函数,以此类推
        getfenv(f)
            1.参数f的使用方法与setfenv同样
            2.返回当前环境的table
        setfenv(1, {})若是这样调用则全部环境变量都将没法访问,包括如print函数,由于环境table是空的
         继承环境环境:
            envt = {}
            setmetatable(envt, {__index = _G})
            setfenv(1, envt)
            print("hello")
         在程序块中调用setfenv(1,table)就会修改当前程序块的环境
         每一个新建立的函数(包括闭包)的环境都默认继承了建立者的环境
         当改变当前环境后,后续建立的函数的环境都将随之改变
         利用默认继承的功能方便 的实现命名空间之类的功能
         能够来回切换多个环境来实现多套变量定义和值内容
         函数在查找要访问的变量时老是以这种顺序,局部->全局->环境table元表的__index来查找
         环境主要用于加载模块等环节,另外的一个主要用途就是看成一个独立的数据存储集全
 
模块与包
  使用require加载模块
    一个规范的模块应该返回一个table做为这个模块全部导出功能的集合
    lua里没经过任何强制性语法规则要求建立模块时反回一个table
    但最好这么作,由于大多lua的模块都是这么发布的
    --require的实现源代码
    function require(name)
        if not package.loaded[name] then
            local loader = findloader(name)
            if loader == nil then
                error("unable to load module "..name)
            end
            package.loaded[name] = true
            local res = loader(name)
            if res ~= nil then
                package.loaded[name] = res
            end
        end
        return package.loaded[name]
    end
    require的内部实现就是经过loadfile或loadlib来加载程序块
    因此加载模块就当相于把程序块看作一个函数
    而后使用模块的文件名做参数调用一次就加载完成了
    只要使用require加载过一次的模块就不会再重复加载了,除非手动把package.loaded[name]=nil
    已加载的模块记录在package.loaded的table里
    loader是一个加载器,它会从package.preload里找对应的加载函数来实施加载
    require"mod"会获得一个全局的mod变量
    local m = require"mod"能够重定义导入模块的名称
    require的加载路径如:
    ?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
    require会以模块名来替换全部"?",每一个条目用";"隔开
    也就是说require只处理分号和问号
    require用于搜索lua文件的路径存放在变量package.path中
    当lua启动后便以环境变量LUA_PATH来初始化这个变量
    若是没有找到环境变量LUA_PATH就以编译时定义的一个默认常是值来初始化
    LUA_PATH里的";;"子串会替换成默认路径
    若是require找不到lua文件就会去找c程序库,路径放在package.cpath里用LUA_CPATH初始化
    在加载c程序库里若是模块名如a-b,则以b为模块加载
    若是一个模块名为mod.sub则认为sub是mod的子模块,在require时会使用配置好的分隔符替换"."
    若是mod.sub会替换为mod\sub这样就能够很容易的分目录放置了
  使用module建立模块
    手工写法
      --建立模块--------------------------------------------------------------------------------         --require会把文件名当参数传进来,用这个变量记下来就好了
      local modname = ...
 
      --建立一个local的table用于记录导出内容
      local _M = {}
 
      --导出的table就等于这个local的table
      _G[modname] = _M
 
      --有了这句就不用return _G[modname]给require了
      package.loaded[modname] = _M
 
      --设置本环境继承全局环境,否则链接print都不能用,
      --其实若是在模块中要用全局内容,可能赋给local变量而后使用
      setmatetable(_M, {__index = _G})
 
      --把这个local的table调成当前环境
      setfenv(1, _M)
 
      --模块内容 ------------------------------------------------------------------------------          --因为使用了环境,因此local根本就不会进入环境,也就不会导出了
      local function test001()
            print("test001")
      end
 
      --全局函数至关于_M.test002=function()end会导出
      function test002()
            print("test002")
      end
    内建module关键字
      --这一句把上面建立模块部分的代码都包括了,没有package.seeall就不继承全局环境                      module(...,package.seeall)
面向对象编程
   对象的实现
    在lua中table就是一种对象
        1.有本身的状态
        2.有本身的惟一标识self
         3.有本身的生命周期
    使用table能够本身实现面向对象的几乎全部特性
    把函数定义在table中,并使用t.func的形式访问,如同方法调用
    Account = {balance=0}
    function Account.withdraw(v)
        Account.balance = Account.ballance - v
    end
    但在函数中使用全局的Account是一个很差的习惯
    在lua中使用面向对象方式编程时尽可能使用self和t:func的形式    
    带有标识自身对象的方法定义:
    function Account.withdraw(self, v)
    同上的语法糖定义:
    function Account:withdraw(v)
    带有标识自身对象的方法调用:
    a1.withdraw(a1, v)
    同上的语法糖定义:
    a1:withdraw(v)
    使用":"会把自身当作每个参数隐式的传入
    使用self是面向对象编程的一大核心,不少语言为程序员隐藏了这个参数
    self在C++中就至关于this指针
   类的实现    
    在lua中没有类的概念,但能够本身来实现
    function Account:new(o)
        o = o or {} --若是用户没有提供table,则建立一个
        setmetatable(o, self)
        self.__index = self
        return o
    end
    当使用new函数来建立对象后(其实就是建立一个新的table),全部的访问都会从Account这个table里找
     这种状况就至关于Account是一个类,也能够说是一个原型模具,全部新建立的table都拥有他的属性和方法
    a = Account:new{balance=0}
    a:deposit(100.00)
    因为deposit在新建立的table a里没有定义
    所以经过它的元表__index来查找,a的元表是Account,
    所以会调用Account的deposit方法,但self传入的是a
    这就实现了a继承了Account的方法deposit
    在这里也看到了使用self来标识调用对象的好处
   继承和派生
    sa = Account:new()
    s = sa:new{limit=1000.00}
    第一行sa继承了Account,sa的元表是Account,找不到的方法就去Account里去找
    第二行s继承了sa,这里的new是Account的方法但传入的self是sa,
    导致s的元表是sa而sa的元表又是Account
    因此一层一层的继承了下去,而且在每一层的派生table里均可以定义重载方法和新的方法
    在lua里的能够实现多重继承,就是使元表的__index指向一个函数,而后自行判断并处理
   私密性
    使用table来实现面向对象的编程方式,几乎能够实现全部面向对象的编程特性
    但它没有也不想去实现的就是对象的私密性,也就是c++里的private、public、protected
    这与lua设计的初衷有关,lua定位于小型的程序开发,参与一个工程的人不会不少,自行约束
    非要实现私密性的话lua也不是不能,只是不能再使用table和元表的方式了
    可使用函数闭包来实现私密性:
    function newAccount(init)
         local self = {blance=init}
         local withdraw = function(v)
             self.balance = self.balance - v
         end
         local deposit = function(v)
             self.balance = self.balance + v
         end
         return{withdraw = withdraw, deposit = deposit}
    end
    在闭包里定义一个table的upvalue,而后把全部闭包函数都定义在这里table里,
    而后返回这个table,用key访问内部方法
    使用闭包实现对象的方式比用table效率高并实现了绝对的私密性,但没法实现继承,至关于简单的小对象
    甚至能够在闭包里仅定义一个方法,而后经过key来判断调用是什么方法
    Tcl/Tk对它的窗口部件就使用这种方法
 
弱引用table
    lua使用自动内存管理机制,经过垃圾回收器来回收内存
    垃圾回收器只能回收它认为是垃圾的内容,而不能回收用户认为是垃圾的内容
    典型的例子栈,栈通常用一个数组和一个表示顶部的索引值表示
    若是弹出一个元素,那么仅是把栈顶索引减一,
    但这个元素还留在内存在被这栈数组引用着,因此垃圾回收器不知道它是垃圾
    全局变量和table里的内容会被垃圾回收器回收,只能手动置为nil
    所以须要一种与回收器能够协做的机制,这就是弱引用(weak reference)
    lua里用弱引用table(weak table)来实现这个机制
    3种弱引用table
        1.具备弱引用key的table
        2.具备弱引用value的table
        3.同时具备弱引用key和弱引用value的table
    不管是哪一种类型的弱引用table,只要有一个key或value被回收了
    那么他们所在的整个条目都会从table中删除
    table的弱引用类型经过其元表中的__mode字段来决定,这个字段是一个字符串
       1.__mode字段中若是包含"k"则是key的弱引用
       2.__mode字段中若是包含"v"则是value的弱引用
       3.__mode字段中包含"k"和"v"则是key、value的弱引用
             a={}
             b={__mode="k"}
             setmetatable(a,b)    --table a的key就是弱引用
             key={}               --建立第一个key 
             a[key]=1
             key={}               --建立第二个key
             a[key]=2
             collectgarbage()     --强制进行一次垃圾收集 
             for k,v in pairs(a) do print(v) end
              --> 2
    第二次key={}会覆盖第一个key,这时再没有对第一个key的引用了,因此会回收
    第二个key因为还被变量key引用,因此不会回收
    lua只会回收用弱引用table中的对象
    若是key是number、boolean、string则不会回收,因此上例中用table来当key
    可使用弱引用table来实现缓存等机制,热数据不会被回收,不用的数据自动释放
math库
    定义在math中
    全部三角函数都使用弧度
    指数和对数函数
    取整函数
    伪随机数math.random
        调用时没有参数返回0~1之间的随机实数
        调用时仅一个整数参数n,则返回1~n之间的随机整数
        调用时若是有两个整数参数m,n,则返回m~n之间的随机整数
        math.randomseed能够设置伪随机数种子
        math.random使用的就是C的rand函数
    数学常量
        pi表示圆周率
        huge表示最大数字
table库
    table.insert
        插入元素到数据指定位置
        它会移动后续元素以空出空间
        t = {10,20,30}
        table.insert(t, 1, 15)
        --t = {15, 10, 20, 30}
        若是没有位置参数(第二个参数),将会把元素插到数组末尾
    table.remove
        删除数组指定位置上的元素,并将该位置后的全部元素前移
        没有指定位置参数则最后一个元素
    table.sort
        对数组进行排序
        第一个在第二个前则返回true
    table.concat
        链接字符串数组中的全部字符串
        有一个参数能够指定插在每一个字符串之间的分隔符
相关文章
相关标签/搜索