• Lua学习


    写在前面:此文只是本人挑选出来的知识点,不是全面的基础学习

      建议有lua基础的读者先读一位大神的系列文章,写得很好:

      Lua入门系列|果冻想:http://www.jellythink.com/archives/882,  

      本文的信息来源主要有:1.Lua入门系列|果冻想

                 2.Lua官方文档(我记得有人翻译了lua部分的接口,c部分的没翻译完,读者可以自己去找找)

                 3.Lua和c的交互:http://www.cnblogs.com/sevenyuan/p/4511808.html(lua和c#的交互也是类似)

                 4.项目代码和自己的思考

    1.lua元表和元方法:

      __add(a, b) --加法
      __index(a, b) --索引查询
      __newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)
      __call(a, ...) --执行方法调用
      __tostring(a) --字符串输出
      __metatable --保护元表

      _index元方法:
        是否还记得当我们访问一个table中不存在的字段时,会返回什么值?默认情况下,当我们访问一个table中不存在的字段时,得到的结果是nil。但是这种状况很容易被改变;Lua是按照以下的步骤决定是返回nil还是其它值得:

        1.当访问一个table的字段时,如果table有这个字段,则直接返回对应的值;
        2.当table没有这个字段,则会促使解释器去查找一个叫__index的元方法【应该说是table的元表的__index元方法】,接下来就就会调用对应的元方法,返回元方法返回的值;
        3.如果没有这个元方法,那么就返回nil结果。
      在实际编程中,__index元方法不必一定是一个函数,它还可以是一个table。
      当它是一个函数时,Lua以table和不存在key作为参数来调用该函数,这就和上面的代码一样;
      当它是一个table时,Lua就以相同的方式来重新访问这个table

      __metatable方法:

        local set = {}
        setmetatable(set, mt)
        mt.__metatable = "You cannot get the metatable" -- 设置完我的元表以后,不让其他人再设置

      PS:不要搞混了元表和元方法:
        元方法是元表里的函数,local a = {};a.__index = function()...这个__index还不是a的元方法!,当a以元表的身份出现时才是,比如local b={},setmetatable(b,a),虽然“这个__index还不是a的元方法”的说法有失偏颇,但可以帮忙理解,赋值元方法是元表的方法,不是子表的。

        我在tolua的源码中看到类似的语法:

        local table_a = {}
        table_a.__index = table_a
        local b = {}
        setmetatable(b,table_a)

        //其实代码改成这样或许会好看一些:
        local table_a = {}
        local table_base = {}//函数,字段的定义表     table_a.__index = table_base
        local b = {}
        setmetatable(b,table_a)

        我当时懵逼了,觉得这不是死循环的写法吗?然后捶了自己的胸口:table_a是元表,这个元表并没有自己的元表,所以不会死循环,寻值过程是b中找不到,去b的元表的__index找,找不到?去b的元表的元表的__index找(但这里,b的元表没有元表,所以这一步就没有到达,返回Nil了)

        元表的元表是自己才会引起死循环,看下面糟糕的代码:

        local table_a = {}
        table_a.__index = table_a
        setmetatable(a,a)

    2.

    index:取下标操作用于访问 table[key] 。

    function gettable_event (table, key)
    local h
    if type(table) == "table" then
    local v = rawget(table, key)
    if v ~= nil then return v end
    h = metatable(table).__index --记住,是元表的__index方法,不是table的__index方法,所以元表一般都定义了__index
    if h == nil then return nil end
    else
    h = metatable(table).__index
    if h == nil then
    error(···);
    end
    end
    if type(h) == "function" then
    return h(table, key) -- 调用处理器
    else return h[key] -- 或是重复上述操作
    end
    end

    __call元方法的调用时机:
    > function f(tb,x,y) return x+y+tb.n end
    > b={}
    > b.__call = f
    > a = {}
    > a.n=100
    > setmetatable(a,b)
    > print(a(1,2)) --103,有点构造函数的味道,但却明显不是!只是执行一个函数,没有创建实例,不过__call经常被赋值成构造函数,这时就是了。

    3.

    require用于使用模块,module用于创建模块
    require:
    require一个模块:
      1.先判断package.loaded这个table中有没有对应模块的信息;
      2.如果有,就直接返回对应的模块,不再进行第二次加载;
      3.如果没有,就加载,返回加载后的模块
      4.搜索package.path的所有路径下,该模块名的Lua文件是否存在,不存在则搜索package.cpath的所有路径下的,该模块名的C程序库文件是否存在,还不存在就报错返回了,当找到了这个文件以后,如果这个文件是一个Lua文件,它就通过loadfile来加载该文件;如果找到的是一个C程序库,就通过loadlib来加载,这两都只加载不运行
      5.require会将加载的文件内容(函数,global变量等)以表的形式存储到table package.loaded[模块名]中
      6.require以模块名为参数 运行 已经加载的文件内容
      7.如果运行没有返回值(即return 结尾),require就会返回table package.loaded中的值
      注:require一个c程序库时,lua会去调用该库的luaopen_xx函数:require "mLualib"调用luaopen_mLualib函数,具体是:
        local path = "mLualib.dll"
        local f = package.loadlib(path,"luaopen_mLualib") -- 返回luaopen_mLualib函数
        f() -- 执行
        可以看一下lua层调用c层dll的例子。
    module:
    module("player_t")内部做了:
      1.模块名设为player_t;
      2.建立一个空table;
      3.在全局环境_G中添加模块名对应的字段,将空table赋值给这个字段;
      4.在已经加载table中设置该模块;
      5.设置环境变量。(该模块就不能访问_G环境了)
    module(..., package.seeall):
      这句话的功能就好比module(...)再加上了setmetatable(M, {__index = _G}),可以正常访问_G
    一个典型的使用例子:

      module( "drop_item_t", package.seeall )--创建一个drop_item_t的模块(在require这个脚本的时候创建)
    
      drop_item_mt = drop_item_mt or { __index = drop_item_t }--创建一个元表,它的__index指向drop_item_t这个全局表(这个表是module语句中创建的),是为了使用drop_item_t表里的函数
      setmetatable( drop_item_t, { __index = ctrl_t } )--drop_item_t元表设为ctrl_t,其中ctrl_t是另外一个全局表,这句的意义是让drop_item_t继承ctrl_t表,是为了让drop_item_mt使用ctrl_t的函数
      function new( _item_id )
        local drop_item = {}
        setmetatable( drop_item, drop_item_mt ) --创建一个表,设元表为drop_item_mt,则这个表可以使用表drop_item_t和表ctrl_t的所有函数
        return drop_item 
      end

      注:这里我一开始也迷惑__index的使用,但看了官方文档即前面第2点的代码后就知道,元表只是“中介”,真正起作用的是元表中的元方法,如__index用于搜寻

    4.

    闭包是由函数和与其相关的引用环境组合而成的实体,即
    闭包=函数+引用环境,多个闭包之间木有联系和影响,他们引用的是不同的环境

    function Fun1()
    local iVal = 10 -- upvalue
    function InnerFunc1() -- 内嵌函数
    print(iVal) --
    end
    
    function InnerFunc2() -- 内嵌函数
    iVal = iVal + 10
    end
    return InnerFunc1, InnerFunc2
    end
    
    -- 将函数赋值给变量,此时变量a绑定了函数InnerFunc1, b绑定了函数InnerFunc2
    local a, b = Fun1()--生产一个闭包
    local c, d = Fun1()--又生产一个闭包
    -- 调用a
    a() -->10
    
    -- 调用b
    b() -->在b函数中修改了upvalue iVal
    
    -- 调用a打印修改后的upvalue
    a() -->20
    --调用c,不受第一个闭包影响
    c() -->10

    闭包一个重要用途是用于迭代

    5.

    协程:
      协程的resume和yield配合起来,在主程序和协同程序之间交换数据:resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
      create后第一次resume称为启动协程,后面的resume叫重新唤醒协程,而每个yield都是一样的。resume分成“开始resume”,和“resume返回”;yield分成“遇到yield”和“重启yield”
      当resume是启动协程时,开始resume就是运行创建协程时传入的函数,resume返回 则返回true/false(出错时false),和 遇到yield时yield的参数:

    co = create(f());
    f(){return yield(1,2,3)}
    resume(co);--返回true,1,2,3,即遇到yield把协程数据传到主线程
    --当resume是重新唤醒协程时,开始resume就是重启yield,这时,resume的第2,3...个参数会借助yield传入协程中:
    co = create(f());
    f(){local a, b = yield(1,2,3);print(a,b);}
    resume(co);返回true,1,2,3
    resume(co,"a","b");//print(a,b)打印的结果是"ab",即resume把数据传入了协程中,重启yield是入口,协助了数据的传入。

      一个“遇到yield把协程数据传到主线程”的例子:
      生产者消费者模式:

    function produter()
    local i=0
    while true do
    i=i+1
    yield(i)
    end
    end
    co = create(produter);
    local consume = resume(co);--每次resume(co)都能生产一个产品(i)

      一个“开始resume即重启yield把主线程数据传入协程”的例子:
      协程缓存:有一个任务需要不定时执行,并且这个任务需要协程执行:

    co = coroutine.create(function (f)
    while f do
    f = coroutine.yield(f())
    end
    end)

      每次resume就执行一次任务,但只需create一个协程,即协程缓存。

    6.

    弱表的使用例子:能解决,缓存的内存开销大过带来的性能提升,问题

    function memoize (f)
    local mem = {} -- 缓存化表
    setmetatable(mem, {__mode = "kv"}) -- 设为弱表
    return function (x) -- ‘f’缓存化后的新版本
    local r = mem[x]
    if r == nil then --没有之前记录的结果?
    r = f(x) --调用原函数
    mem[x] = r --储存结果以备重用
    end
    return r
    end
    end

    7.

    记住这张图:

    lua和c/c++交互:强烈建议看这篇博文:http://www.cnblogs.com/sevenyuan/p/4511808.html

    因为行文简单明了:简单而没有废话,行文思想也很有趣,但有几句话不妥:

      1.类似的还有lua_setfield,设置一个表的值,肯定要先将值出栈,保存,再去找表的位置。

        应该是先设置完值后,才出栈,不然干嘛压栈,不就为了在lua层new一个变量,然后给表设置使用吗?,出栈就毁了。

      2.lua_getglobal(L,"var")会执行两步操作:1.将var放入栈中,2.由Lua去寻找变量var的值,并将变量var的值返回栈顶(替换var)。

        应该是先把_G压栈...

    8.待续。。。

  • 相关阅读:
    Java基础——链表的添加、移除、反转
    Java基础——数组队列的原理和实现
    Java基础——String、StringBuiler、StringBuffer的用法和区别
    Java基础——解决JFrame.setBackground设置无效,mac系统IDEA编译器
    Java基础——文件查找创建删除
    Java基础——Java异常机制
    线性表的操作
    Linux目录及文件系统操作
    Linux用户及文件权限管理
    Linux日志系统
  • 原文地址:https://www.cnblogs.com/Tearix/p/6919171.html
Copyright © 2020-2023  润新知