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.待续。。。

posted @ 2017-05-29 22:06  BigTreee  阅读(492)  评论(0编辑  收藏  举报