Lua学习笔记
Lua 学习笔记 v1.0通过__《Programming in Lua》__和**《Lua程序设计》**以及网上关于Lua的相关资料,进行学习整理,总结出这一份学习笔记。为后面的初学者提供参考。 >本资料针对的是Lua5.1版本,5.2版本以后语法和库发生了显著变化,需要额外引起注意。 <a name='TOC'>目录表</a>
<a name='profile'>简介</a>背景介绍Lua作为目前最为流行的、免费轻量级嵌入式脚本语言,在很多工业级的应用程序中被广泛应用,如Adobe's Photoshop,甚至是在一些著名的游戏程序中也被大量使用,如星际。不仅如此,由于Lua具备很多特殊的优点,如语法简单(基于过程)、高效稳定(基于字节码)、可以处理复杂的数据结构、动态类型、以及自动内存管理(基于垃圾收集)等,因此在很多嵌入式设备和智能移动设备中,为了提高程序的灵活性、扩展性和高可配置性,一般都会选择Lua作为它们的脚本引擎,以应对各种因设备不同而带来的差异。 主要优势
[?] <a name='grammar'>语法</a>注释写一个程序,总是少不了注释的。 example1: dispatcher.lua --[[ LuCI - Dispatcher Description: The request dispatcher and module dispatcher generators FileId: $Id$ License: Copyright 2008 Steven Barth <steven@midlink.org> Licensed under the Apache License,Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing,software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express or implied. See the License for the specific language governing permissions and limitations under the License. ]]-- --- LuCI web dispatcher. ... ... 关键字Lua语法是非常简单的,关键字也不多,在这里需要注意:关键字是不能做为变量的,其他任何类型都是变量,如函数等。 关键字如下: ---------------------------------------------------------------- and | break | do | else | elseif | end ---------------------------------------------------------------- false | for |function| if | in | local ---------------------------------------------------------------- nil | not | or | while | repeat | return ---------------------------------------------------------------- then | until | true | ---------------------------------------------------------------- 变量类型怎么确定一个变量是什么类型的呢?大家可以用type()函数来检查。Lua支持的类型有以下几种: 1. Nil | 空值类型,所有没有使用过的变量都是nil类型,nil既是值,又是类型 2. Boolean | 布尔类型,只有两个有效值:true 或false 3. Number | 数值类型,在Lua里,数值表示实数,相当于C语言中的double类型 4. String | 字符串类型,字符串是可以包含" "字符的(与C语言不同) 5. Table | 关系表类型,Lua中的核心概念,后续介绍 6. Function| 函数类型,在Lua中函数也是一种类型,也就是说,所有的函数,本身也是一种变量。 7. Userdata| 用户数据类型,和宿主语言进行数据交换的,这里我们不做重点介绍 8. Thread | 线程类型, 在Lua中是通过协程coroutine实现的 在example2中,我可以用type函数输出变量类型。 example2: type.lua --[[ FileName: type.lua Author : John.Wang Descriptions : print the type of Lua ]]-- print(type(nil)) --> nil print(type(true)) --> boolean print(type(1.1)) --> number print(type(""))--> string print(type({}))--> table print(type(function () end)) -->function print(type(coroutine.create(function() end))) -->thread 变量定义所有的语言,都要用到变量,对于大部分动态语言来说,变量可以不用预先声明,直接使用,动态地获取自己的类型。Lua也是这样。 全局变量_G在Lua脚本中,Lua将所有的全局变量保存在一个常规的table中,这个table被称为全局环境,并且将这个table保存在一个全局变量 [?] <a name='control_statement'>控制语句</a>在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。 -------------------------------------------------------------------------------------------- 控制语句 | 格式 | 示例 -------------------------------------------------------------------------------------------- If | if 条件 then ... elseif then ... else ...end | if true then print("true") end -------------------------------------------------------------------------------------------- While | while 条件 do ... end | while true do print("true") end -------------------------------------------------------------------------------------------- Repeat | repeat ... until 条件 | repeat print("true") until false -------------------------------------------------------------------------------------------- For | for 变量=初值,终点值,步长值 do ... end | for i=1,10,2 do print(i) end -------------------------------------------------------------------------------------------- For | for 变量1,变量2,...变量n in 表或者枚举函数 do... end | for k,v ipairs(_G) print(k,v) end -------------------------------------------------------------------------------------------- >注意一下,for的循环变量总是只作用于for的局部变量;当省略步进值时,for循环会使用1作为步进值。 使用break可以用来中止一个循环。 [?] <a name='rvalue'>赋值语句</a>赋值语句在Lua被强化了。它可以同时给多个变量赋值。 a,b,c,d=1,2,3,4 甚至是: a,b=b,a -- 多么方便的交换变量功能啊。 在默认情况下,变量总是认为是全局的。假如需要定义局部变量,则在第一次赋值的时候,需要用local说明。比如: local a,c = 1,3 -- a,c都是局部变量 这里面需要注意的是,不能这样的赋值 如, [?] <a name='biaodashi'>表达式</a>数值运算和C语言一样,支持 +,-,*,/。但Lua还多了一个"^"。这表示指数乘方运算。比如2^3 结果为8,2^4结果为16。 比较运算比较符号 | < | > | <= | >= | == | ~= | 符号意义 | 小于 | 大于 | 小于等于 | 大于等于 | 等于 | 不等于 | 所有这些操作符总是返回true或false。 a={1,2} b=a print(a==b,a~=b) --输出 true,false a={1,2} b={1,2} print(a==b,a~=b) --输出 false,true 逻辑运算and,or,not 举几个例子: print(4 and 5) --输出 5 print(nil and 13) --输出 nil print(false and 13) --输出 false print(4 or 5) --输出 4 print(false or 5) --输出 5 在Lua中这是很有用的特性,也是比较令人混洧的特性。 但是上面 运算符优先级,从低到高顺序如下: or and < > <= >= ~= == .. (string concat) + - * / % not #(取长度运算符,lua 5.1) -(一元运算) ^ >和C语言一样,括号可以改变优先级。 [?] <a name='functions'>函数</a>基础函数函数,在Lua中,函数的定义也很简单。典型的定义如下: function add(x,y) -- add是函数名,x,y 是参数名 return x + y -- return用来返回函数的运行结果 end >请注意,return语言一定要写在end之前。假如我们非要在中间放上一句return,那么就应该要写成: >需要说明的是,Lua中实参和形参的数量可以不一致,一旦出现这种情况,Lua的处理规则等同于多重赋值,即实参多于形参,多出的部分被忽略,如果相反,没有被初始化的形参的缺省值为nil。 还记得前面说过,函数也是变量类型吗?上面的函数定义,其实相当于: add = function (x,y) return x+y end 当重新给add赋值时,它就不再表示这个函数了。我们甚至可以赋给add任意数据,包括nil (这样,赋值为nil,将会把该变量清除)。在Lua中Function很像C语言的函数指针呢? 和C语言一样,Lua的函数可以接受可变参数个数,它同样是用"..."来定义的,比如: function sum(x,y,...) 如果想取得...所代表的参数,可以在函数中访问arg局部变量(表类型)得到 (lua5.1: 取消arg,并直接用"..."来代表可变参数了,本质还是arg)。 Lua中,函数可以返回多个值,这点和python相似,但是python实际上是返回一个元组而已,不是真正意义上的多值。 function s() return 1,4 end a,d = s() -- 此时,a = 1,b = 2,c = 3,d = 4 命名参数 高阶函数Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。 词法定界 闭包 看下面这个例子: -- 生成函数,返回一个显示n次c字符的closure function rep_char(c,n) -- 特别注意这个 local 否则fun就是global,后面的递归就错了. local function fun() if n > 0 then print (c); -- 递归显示 n = n-1; fun(); end end return fun; end -- 生成两个closure f1 = rep_char("A",3); f2 = rep_char("B",5); -- 调用 f1(); f2(); 尾调函数 function f(x) return g(x) end 由于g(x)函数是f(x)函数的最后一条语句,在函数g返回之后,f()函数将没有任何指令需要被执行,因此在函数g()返回时,可以直接返回到f()函数的调用点。由此可见,Lua解释器一旦发现g()函数是f()函数的尾调用,那么在调用g()时将不会产生因函数调用而引起的栈开销。这里需要强调的是,尾调用函数一定是其调用函数的最后一条语句,否则Lua不会进行优化。然而事实上,我们在很多看似是尾调用的场景中,实际上并不是真正的尾调用,如: function f(x) g(x) end --没有return语句的明确提示 function f(x) return g(x) + 1 --在g()函数返回之后仍需执行一次加一的指令。 function f(x) return x or g(x) --如果g()函数返回多个值,该操作会强制要求g()函数只返回一个值。 function f(x) return (g(x)) --原因同上。 在Lua中,只有" <a name='tables'>表</a>关系表类型,这是一个很强大的类型。我们可以把这个类型看作是一个数组。只是C语言的数组,只能用正整数来作索引;在Lua中,你可以用任意类型来作数组的索引,除了nil。同样,在C语言中,数组的内容只允许一种类型;在Lua中,你也可以用任意类型的值来作数组的内容,除了nil。 T1 = {} -- 定义一个空表 T1[1]=10 -- 然后我们就可以象C语言一样来使用它了。 T1["John"]={Age=27,Gender="Male"} 这一句相当于: T1["John"]={} -- 必须先定义成一个表,还记得未定义的变量是nil类型吗 T1["John"]["Age"]=27 T1["John"]["Gender"]="Male" 当表的索引是字符串的时候,我们可以简写成: T1.John={} T1.John.Age=27 T1.John.Gender="Male"或 T1.John{Age=27,Gender="Male"} 这是一个很强的特性。 在定义表的时候,我们可以把所有的数据内容一起写在"{"和"}"之间,这样子是非常方便,而且很好看。比如,前面的T1的定义,我们可以这么写: T1 = { 10,-- 相当于 [1] = 10 [100] = 40, John= -- 如果你原意,你还可以写成:["John"] = { Age=27,-- 如果你原意,你还可以写成:["Age"] =27 Gender=Male -- 如果你原意,你还可以写成:["Gender"] =Male }, 20 -- 相当于 [2] = 20 } 我们在写的时候,需要注意三点: 表类型的构造是如此的方便,以致于常常被人用来代替配置文件。是的,不用怀疑,它比ini文件要漂亮,并且强大的多。 [?] <a name='metatables_metamethods'>元表和元方法</a>元表什么是元表Lua中Metatable这个概念,国内将他翻译为元表. 元表为重定义Lua中任意一个对象(值)的默认行为提供了一种公开入口. 如同许多OO语言的操作符重载或方法重载. Metatable能够为我们带来非常灵活的编程方式. 具体的说,Lua中每种类型的值都有都有他的默认操作方式,如,数字可以做加减乘除等操作,字符串可以做连接操作,函数可以做调用操作,表可以做表项的取值赋值操作. 他们都遵循这些操作的默认逻辑执行,而这些操作可以通过Metatable来改变. 如,你可以定义2个表如何相加等. 看一个最简单的例子,重定义了2个表的加法操作. 这个例子中将c的 --定义2个表 a = {5,6} b = {7,8} --用c来做Metatable c = {} --重定义加法操作 c.__add = function(op1,op2) for _,item in ipairs(op2) do table.insert(op1,item) end return op1 end --将a的Metatable设置为c setmetatable(a,c) --d现在的样子是{5,6,7,8} d = a + b 有了个感性的认识后,我们看看Metatable的具体特性. Metatable定义的操作有: add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,tostring,gc,index,newindex,call... 在Lua中任何一个值都有Metatable,不同的值可以有不同的Metatable也可以共享同样的Metatable,但在Lua本身提供的功能中,不允许你改变除了table类型值外的任何其他类型值的Metatable,除非使用C扩展或其他库. > setmetatable和getmetatable是唯一一组操作table类型的Metatable的方法. Lua的语法非常灵活,使用他的metatable及metamethod可以模拟出很多语言的特性. [?] <a name='object_oriented'>面向对象</a>Lua中,面向对向是用元表这种机制来实现的。元表是个很“道家”的机制,很深遂,很强大,里面有一些基本概念比较难理解透彻。不过,只有完全理解了元表,才能对Lua的面向对象使用自如,才能在写Lua代码的高级语法时游刃有余。 首先,一般来说,一个表和它的元表是不同的个体(不属于同一个表),在创建新的table时,不会自动创建元表。 但是,任何表都可以有元表(这种能力是存在的)。 e.g. t = {} print(getmetatable(t)) --> nil t1 = {} setmetatable(t,t1) assert(getmetatable(t) == t1) setmetatable( 表1, 表2) 将表2挂接为表1的元表,并且返回经过挂接后的表1。 元表中的 元表中的
对于没有元表的表,访问一个不存在的字段,就直接返回一个nil了。
从继承特性角度来讲,初步的效果使用 面向对象的实现Lua应该说,是一种原型语言。原型是一种常规的对象,当其他对象(类的实例)遇到一个未知的操作时,原型会去查找这个原型。在这种语言中要表示一个类,只需创建一个专用作其他对象的原型。实际上,类和原型都是一种组织对象间共享行为的方式。 下面是一中类的创建和对象构造的例子。 --账户基类:Account Account = {balance = 100.00} --有一个blance属性 ---模拟Account实例化和继承思想所需的方法new() function Account:new(o) o = o or { } setmetatable(o,self) self.__index = self return o end --Account类的方法deposit(存款) function Account:deposit( v ) self.balance = self.balance + v end --Account类的方法deposit(取款) function Account:withdraw( v ) if v > self.balance then error "no enph money!" end self.balance = self.balance - v end ---------------------------------------------------------------------------------------- --派生子类 spacialAccount = Account:new{limit = 1000.00,rate = 0.100} --子类的新方法 function spacialAccount:setrate( v ) self.rate = v; end --实例化子类,得到对象s s = spacialAccount:new() --对象调用方法 s:setrate(0.300) print(s.balance) print(s.limit) print(s.rate) 子类继承父类,和实例化类产生新对象都用new()方法模拟实现,实现形式一样,但概念是不一样的。 不过在实际工程中,我们并不用上面的方式来构造类和对象。一般我们推荐下面这种方式。 -- class.lua module(...,package.seeall) function class(super) local mt = { __call = function (_c,...) local function ct(_c,_o,...) if _c.__super then ct(_c.__super,...) end if _c.__ctor then _c.__ctor(_o,...) end return _o end local _o = ct(_c,{},...) return setmetatable(_o,_c) end } mt.__index = super or mt return setmetatable({__super=super},mt) end 测试用例: --test.lua local class = require "class".class -- define a class Type A A = class() -- set constructor method of class A function A:__ctor(s) self.i = 1 self.j = 2 print("A ctor",s) end -- instance of class A a = A('a') print(a.i,a.j) -- output -- -- A ctor a -- 1 2 ---------------------------------------- -- define class B inherited class A B = class(A) -- set constructor method of class B function B:__ctor(s) self.z = 3 print("B ctor",s) end -- instance of class B b = B('b') print(b.i,b.j,b.z) -- output -- -- A ctor b -- B ctor b -- 1 2 3 对象构造实例下面我们根据上面的介绍,来构造一个Set集合类,这个类,提供了集合的一些操作,如合集,插入,输出等。 首先需要构造一个类的构造器,元类,也就是所有类的父类。 --[[ -- object.lua -- This is object class ]]-- module(...,package.seeall) function class(classname,super) local cls = {} if super then cls = {} for k,v in pairs(super) do cls[k] = v end cls.super = super else cls = {ctor = function () end} end cls.__cname = classname cls.__index = cls function cls.new(...) local instance = setmetatable({},cls) local function create(c,...) if c.super then create(c.super,...) end if c.ctor then c.ctor(instance,...) end end create(instance,...) instance.class =cls return instance end return cls end 然后,我们用类的构造器,来生成我们需要的具体类,这里我们生成一个集合类Set. 如 --[[ -- Set.lua -- Set class for lua ]]-- module("Set",package.seeall) local class = require "object".class Set = class("Set",nil) metatable = {} function Set:ctor(l) self.__set = setmetatable({},metatable) for _,v in ipairs(l) do self:insert(v) end end function Set:insert(i) self.__set[i] = true end function Set:show() for i,_v in pairs(self.__set) do print(i) end end ... 下面我们写一个例子,来测试一下Set集合。 例 --[[ -- testSet.lua ]]-- local Set = require("Set").Set s = Set.new({1,3}) -- 构造一个Set类的实例 s:insert(4) -- 实例插入一个元素 s:insert(5) -- 实例插入一个元素 s:show() -- 输出集合实例的所有元素 ... 通过上面的例子,我们应该对Lua面向对象编程有了进一步的认识。 [?] <a name='modules'>模块</a>从Lua 5.1开始,我们可以使用require和module函数来获取和创建Lua中的模块。从使用者的角度来看,一个模块就是一个程序库,可以通过require来加载,之后便得到一个类型为table的全局变量。此时的table就像名字空间一样,可以访问其中的函数和常量,如: require "mod" mod.foo() local m2 = require "mod2" local f = mod2.foo f() require 函数rquire(modname) 此函数先检测package.loaded表中是否存在modname,存在则直接返回当中的值,没有则通过预先定义的加载器加载modname,查找加载器顺序:
通过查找modname.dll,并查找其中的luaopen_开头的。 当require查找的不是一个Lua库或C库,它就会调用all-in-one loader,此加载器是用C路径作为加载的目录。 当查找到合适的加载器时,require就会加载其中的模块,当加载器有返回值,将会存放于package.loaded[modname]表,最后返回package.loaded[modname]表。当加载失败时,require将会触发错误。 package.cpath 功能:用于require C loader 的搜索路径 package.loaded 功能:一个用于让require知道哪些模块已加载的记录表,如果package.loaded已经有require要的值,则直接返回此值。 package.path 功能:用于require Lua loader的搜索路径 package.proload 编写模块的基本方法见如下代码和关键性注释: --将模块名设置为require的参数,这样今后重命名模块时,只需重命名文件名即可。 -- modulename.lua local modname = ... local M = {} _G[modname] = M M.i = {r = 0,i = 1} --定义一个模块内的常量。 function M.new(r,i) return {r = r,i = i} end function M.add(c1,c2) return M.new(c1.r + c2.r,c1.i + c2.i) end function M.sub(c1,c2) return M.new(c1.r - c2.r,c1.i - c2.i) end --返回和模块对应的table。 return M 加载模块代码: require "modulename" 使用环境仔细阅读上例中的代码,我们可以发现一些细节上问题。比如模块内函数之间的调用仍然要保留模块名的限定符,如果是私有变量还需要加local关键字,同时不能加模块名限定符。如果需要将私有改为公有,或者反之,都需要一定的修改。那又该如何规避这些问题呢?我们可以通过Lua的函数“全局环境”来有效的解决这些问题。见如下修改的代码和关键性注释: --模块设置和初始化。这一点和上例一致。 local modname = ... local M = {} _G[modname] = M --声明这个模块将会用到的全局函数,因为在setfenv之后将无法再访问他们, --因此需要在设置之前先用本地变量获取。 local sqrt = mat.sqrt local io = io --在这句话之后就不再需要外部访问了。 setfenv(1,M) --后面的函数和常量定义都无需模块限定符了。 i = {r = 0,i = 1} function new(r,i = i} end function add(c1,c2) return new(c1.r + c2.r,c1.i + c2.i) end function sub(c1,c2) return new(c1.r - c2.r,c1.i - c2.i) end --返回和模块对应的table。 return M module函数module(name [,...]) 当 package.seeall(module) 等效于: setmetatable(_M,{__index = _G}) 在Lua 5.1中,我们可以用module(...)函数来代替以下代码,如: local modname = ... local M = {} _G[modname] = M package.loaded[modname] = M --[[ 和普通Lua程序块一样声明外部函数。 --]] setfenv(1,M) 由于在默认情况下,module不提供外部访问,必须在调用它之前,为需要访问的外部函数或模块声明适当的局部变量。然后Lua提供了一种更为方便的实现方式,即在调用module函数时,多传入一个package.seeall的参数,如: module(...,package.seeall) > 注意,Lua5.2版本以后,不支持module函数了,官方推荐自己构造一套require/module机制。 模块构造实例下面我们给出一个模块的构造实例,说明如何构造模块和调用模块。这里我们构造一个Set集合, 这个集合提供了相关的操作接口,如合集,并集,差集等。 如下代码是 -- @module set module ("set",package.seeall) -- Primitive methods (know about representation) -- The representation is a table whose tags are the elements,and -- whose values are true. -- @func member: Say whether an element is in a set -- @param s: set -- @param e: element -- @returns -- @param f: true if e is in set,false otherwise function member (s,e) return s[e] == true end -- @func insert: Insert an element to a set -- @param s: set -- @param e: element function insert (s,e) s[e] = true end -- @func new: Make a list into a set -- @param l: list -- @returns -- @param s: set metatable = {} function new (l) local s = setmetatable ({},metatable) for _,e in ipairs (l) do insert (s,e) end return s end -- @func elements: Iterator for sets -- TODO: Make the iterator return only the key elements = pairs -- High level methods (representation unknown) -- @func difference: Find the difference of two sets -- @param s,t: sets -- @returns -- @param r: s with elements of t removed function difference (s,t) local r = new {} for e in elements (s) do if not member (t,e) then insert (r,e) end end return r end -- @func difference: Find the symmetric difference of two sets -- @param s,t: sets -- @returns -- @param r: elements of s and t that are in s or t but not both function symmetric_difference (s,t) return difference (union (s,t),intersection (t,s)) end -- @func intersection: Find the intersection of two sets -- @param s,t: sets -- @returns -- @param r: set intersection of s and t function intersection (s,t) local r = new {} for e in elements (s) do if member (t,e) end end return r end -- @func union: Find the union of two sets -- @param s,t: sets -- @returns -- @param r: set union of s and t function union (s,t) local r = new {} for e in elements (s) do insert (r,e) end for e in elements (t) do insert (r,e) end return r end -- @func subset: Find whether one set is a subset of another -- @param s,t: sets -- @returns -- @param r: true if s is a subset of t,false otherwise function subset (s,t) for e in elements (s) do if not member (t,e) then return false end end return true end -- @func propersubset: Find whether one set is a proper subset of -- another -- @param s,t: sets -- @returns -- @param r: true if s is a proper subset of t,false otherwise function propersubset (s,t) return subset (s,t) and not subset (t,s) end -- @func equal: Find whether two sets are equal -- @param s,t: sets -- @returns -- @param r: true if sets are equal,false otherwise function equal (s,t) and subset (t,s) end -- @head Metamethods for sets -- set + table = union metatable.__add = union -- set - table = set difference metatable.__sub = difference -- set * table = intersection metatable.__mul = intersection -- set / table = symmetric difference metatable.__div = symmetric_difference -- set <= table = subset metatable.__le = subset -- set < table = proper subset metatable.__lt = propersubset 我们编写一个用例 require "set" s = set.new({1,3}) -- 创建一个集合,并初始化为{1,2,3} s2 = set.new({3,4,5,2}) -- 创建一个集合 s3 = set.union(s,s2) -- 两个集合求合集,这里可以直接写 s3 = s + s2 set.insert(s3,11) -- 集合中插入一个元素 for k,_ in pairs(s3) do print(k) end -- 打印集合中的所有元素 [?] <a name='coroutines'>协程</a>###coroutine基础 Lua所支持的协程全称被称作协同式多线程(collaborative multithreading)。Lua为每个coroutine提供一个独立的运行线路。然而和多线程不同的地方就是,coroutine只有在显式调用yield函数后才被挂起,同一时间内只有一个协程正在运行。 Lua将它的协程函数都放进了coroutine这个表里,其中主要的函数如下: ----------------------------------------------------------------------------------------------------------------------- 函数名 函数参数 函数返回值 函数作用 ----------------------------------------------------------------------------------------------------------------------- coroutine.create(f) 接受单个参数,这个参数 然后返回它的控制器, create函数创建一个新的 是coroutine的主函数 一个对象为thread的对象 coroutine,定义了协同内 任务流程。 ----------------------------------------------------------------------------------------------------------------------- coroutine.resume(co,第一个参数:coroutine.create 如果程序没有任何运行错误 当第一次调用coroutine的 [,val1,...]) 的返回值,即一个thread对象。 的话,返回true,之后的返回值 resume方法时,coroutine 第二个参数:coroutine中执行 是前一个调用coroutine.yield 从主函数的第一行开始执行, 需要的参数,是一个变长参数, 中传入的参数,如果有错误的话, 之后在coroutine开始运行后, 可传任意个。 返回false,加上错误信息。 它会一直运行到自身终止或者是 coroutine函数的下一个yield函数 ----------------------------------------------------------------------------------------------------------------------- coroutine.yield(...) 传入可变参数 返回在前一个调用coroutine.resume() 挂起当前执行的协同,这个协同不能 中传入的参数值 是一个C函数,一个元表或 者一个迭代器 ----------------------------------------------------------------------------------------------------------------------- coroutine.running() 空 返回当前正在运行的协同,如果它被主 线程调用的话,会返回nil ----------------------------------------------------------------------------------------------------------------------- coroutine.status() 空 返回当前协同的状态:有running,suspended,dead ----------------------------------------------------------------------------------------------------------------------- 下面是一段例子代码: function foo(a) print("foo",a) return coroutine.yield(2 * a) end co = coroutine.create(function ( a,b ) print("co-body",a,b) local r = foo(a + 1) print("co-body",r) local r,s = coroutine.yield(a + b,a - b) print("co-body",r,s) return b,"end" end) print("main",coroutine.resume(co,1,10)) print("main","r")) print("main","x","y")) print("main","y")) 下面是运行结果 co-body 1 10 foo 2 main true 4 co-body r main true 11,-9 co-body x y main false 10 end main false cannot resume dead coroutine 先理解下下面几点:
先对照上面几条看一个简单的例子: co = coroutine.create( function (a,b) print("params",b) return coroutine.yield(3,3) end ) coroutine.resume(co,2); print(coroutine.resume(co,5)) 输出结果: params 1 2 true 4 5 理解完上面这个程序,再看看开始的程序,这里把每个输出执行了哪些步骤列出来了:
coroutine进阶Lua中协同的强大能力,还在于通过resume-yield来交换数据:
>协同中的参数传递形势很灵活,一定要注意区分,在启动coroutine的时候,resume的参数是传给主程序的;在唤醒yield的时候,参数是传递给yield的。看下面这个例子: co = coroutine.create(function (a,b) print("co",coroutine.yield()) end) coroutine.resume(co,2) --没输出结果,注意两个数字参数是传递给函数的 coroutine.resume(co,5) --co 1 2 3 4 5,这里的两个数字参数由resume传递给yield Lua的协同称为不对称协同(asymmetric coroutines),指“挂起一个正在执行的协同函数”与“使一个被挂起的协同再次执行的函数”是不同的,有些语言提供对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换”。 >注意:resume运行在保护模式下,因此,如果协同程序内部存在错误,Lua并不会抛出错误,而是将错误返回给resume函数。
为了理解上面两句话的含义,我们来看一下如何利用Coroutine来解决生产者——消费者问题的简单实现: produceFunc = function() while true do local value = io.read() print("produce: ",value) coroutine.yield(value) --返回生产的值 end end consumer = function(p) while true do local status,value = coroutine.resume(p); --唤醒生产者进行生产 print("consume: ",value) end end --消费者驱动的设计,也就是消费者需要产品时找生产者请求,生产者完成生产后提供给消费者 producer = coroutine.create(produceFunc) consumer(producer) 这是一种消费者驱动的设计,我们可以看到resume操作的结果是等待一个yield的返回,这很像普通的函数调用。我们还可以在生产消费环节之间加入一个中间处理的环节(过滤器): produceFunc = function() while true do local value = io.read() print("produce: ",value) coroutine.yield(value) --返回生产的值 end end filteFunc = function(p) while true do local status,value = coroutine.resume(p); value = value *100 --放大一百倍 coroutine.yield(value) end end consumer = function(f,p) while true do local status,value = coroutine.resume(f,p); --唤醒生产者进行生产 print("consume: ",value) end end --消费者驱动的设计,也就是消费者需要产品时找生产者请求,生产者完成生产后提供给消费者 producer = coroutine.create(produceFunc) filter = coroutine.create(filteFunc) consumer(filter,producer) 可以看到,我们在中间过滤器中将生产出的值放大了一百倍。 通过这个例子应该很容易理解coroutine中如何利用resume-yield调用来进行值传递了,他们像“调用函数——返回值”一样的工作,也就是说resume像函数调用一样使用,yield像return语句一样使用。coroutine的灵活性也体现在这种通过resume-yield的值传递上。 [?] <a name='references'>参考</a>
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |