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