Lua的编程实例

来源:互联网 发布:mac图片模糊处理 编辑:程序博客网 时间:2024/05/12 12:29

以前准备写这么篇文章,一直没动手起来,现在在这贴上我编写的一些Lua简单实例源码,跟大家分享下。

转载请注明出处!

 

--[[ 当函数参数很多的时候,这种用表的结构作为实参,是很好的传参方式w = Window {x=0, y=0, width=300, height=200,title = "Lua", background="blue",border = true}function Window(options)-- check mandatory optionsif type(options.title) ~= "string" thenerror("no title")elseif type(options.width) ~= "number" thenerror("no width")elseif type(options.height) ~= "number" thenerror("no height")end-- everything else is optional_Window(options.title,options.x or 0, -- default valueoptions.y or 0, -- default valueoptions.width, options.height,options.background or "white", -- defaultoptions.border -- default is false (nil))--]]--[[逻辑运算符and or not逻辑运算符认为false和nil是假(false),其他为真,0也是true.and和or的运算结果不是true和false,而是和它的两个操作数相关。a and b -- 如果a为false,则返回a,否则返回ba or b -- 如果a为true,则返回a,否则返回b一个很实用的技巧:如果x为false或者nil则给x赋初始值vx = x or v等价于if not x thenx = vendand的优先级比or高。C语言中的三元运算符a ? b : c在Lua中可以这样实现:(a and b) or c--]]--[[构造函数可以使用任何表达式初始化,也可以直接初始化数组。如Lua将第一个元素初始化days[1](第一个元素索引为1)如果想初始化一个表作为记录数据使用可以这样:a = {x=0, y=0}  相当于  a = {}; a.x=0; a.y=0在构造函数的最后的","是可选的,可以方便以后的扩展。a = {[1]="red", [2]="green", [3]="blue",}在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。{x=10, y=45; "one", "two", "three"}如果真的想要数组下标从0开始:days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}注意:不推荐数组下标从0开始,否则很多标准库不能使用。--]]print("------------用表做数组")days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}print(days[3])--输出:Tuesday--[[每次调用构造函数,Lua都会创建一个新的table,可以使用table构造一个list不管用何种方式创建table,我们都可以向表中添加或者删除任何类型的域(即表中的数据可以是八种基本类型中的一种),构造函数仅仅影响表的初始化?也就是说table中的成员可以是 function函数、table表等数据类型(可以参考下面的实例 “一种简单的对象实现方式”)--]]print("-----------使用table表构造一个链表list")list = nil--for line in io.lines() dofor x = 1,10 dolist = {next=list, value=x}endlocal i = 1while i <= 10 doprint(list.value)i = i + 1list = list.nextend--[[ 可以嵌套构造函数来表示复杂的数据结构.--]]--在同一个构造函数中可以混合列表风格和record风格进行初始化,如下polyline = {color="blue", thickness=2, npoints=4,{x=0, y=0},{x=-10, y=0},{x=-10, y=1},{x=0, y=1}}print(polyline[2].x) --输出: -10--[[ 循环语句--]]--数值for循环打印一组数print("-----------------数值for循环")for i = 1, 10 doprint(i)end--[[范型for循环,print all keys of table 't'--]]print("----------------范型for循环")--t = {7,8,34,10,65,23,11}t = {}t2 = {x = 1, ["hello"] = "world!"}t.astring = "ni,hao!"t[1] = 100t["a table"] = bfunction foo()endfunction bar()endt[foo] = bar--分别穷举表t和t2for k,v in pairs(t) doprint(k,"==>",v)endprint("-------------------------")for k,v in pairs(t2) doprint(k, "==>", v)end--[[while循环--]]print("while loop demo")x = 10local i = 1--local to the chunkwhile i <= x dolocal x = i*2--local to the while bodyprint(x)-->2,4,6,8,..i = i + 1endif i > 20 thenlocal x--local to the "then" bodyx = 20print(x + 2)elseprint(x)-->10 (the global one)endprint(x)-->10 (the global one)--repeat-until 循环语句print("repeat ... until 循环语句")--string.format("%q", 'repeat ... until循环语句\n new line!') 可以按指定格式输出字符串,跟C的Printf格式类似,而pint不能指定格式输出local num = 1repeati = 1local n = inum = num + ni = i + 1until i <= 10print(num)--testprint(".....................");local x = 1;if (x > 0) thenprint("test");end--function函数编写和调用print("----------------function函数调用---------")function foo(a, b, c)local sum = a + breturn sum, cendr1, r2 = foo(1, '123', 'hello world')r3,r4 = 10 .."ping", "xiexie"print(r1, r2, r3, r4)--[[Lua也提供了面向对象方式调用函数的语法,比如o:foo(x)与o.foo(o, x)是等价的.下面实例解说1.对象工厂模式如前面代码的create函数2.用表来表示对象把对象的数据和方法都放在一张表内,虽然没有隐藏私有成员,但对于简单脚本来说完全可以接受。3.成员方法的定义function obj:method(a1, a2, ...) … end 等价于function obj.method(self, a1, a2, ...) … end 等价于obj.method = function (self, a1, a2, ...) … end4.成员方法的调用obj:method(a1, a2, …) 等价于obj.method(obj, a1, a2, ...)--]]--一种简单的对象实现方式function PFN_Create(name, id)local C_Obj = {m_name = name, m_id = id}function C_Obj:SetName(name)self.m_name = nameendfunction C_Obj:GetName()return self.m_nameendfunction C_Obj:SetId(id)self.m_id = idendfunction C_Obj:GetId()return self.m_idendreturn C_Objendprint("----------一种简单的对象实现方式")C_O1 = PFN_Create("Sam", 001)print("C_O1'S name:", C_O1:GetName(), "C_O1's id:", C_O1:GetId())C_O1.SetName(C_O1, "SUerge Ping")C_O1.SetId(C_O1, 100)print("C_O1's name:", C_O1.GetName(C_O1), "C_O1's id:", C_O1.GetId(C_O1))--[[简单继承优点:简单、直观缺点:传统、不够动态--]]print("------------------简单继承")function PFN_CreateRobot(name, id)local C_Obj = { m_name = name, m_id = id}function C_Obj:SetName(name)self.m_name = nameendfunction C_Obj:GetName()return self.m_nameendfunction C_Obj:SetId(id)return self.m_idendfunction C_Obj:GetId()return self.m_idendreturn C_Objendfunction PFN_CreateFootballRobot(name, id, position)local C_Obj = PFN_CreateRobot(name, id)C_Obj.m_position = "right back"function C_Obj:SetPosition(position)self.m_position = positionendfunction C_Obj:GetPostion()return self.m_positionendreturn C_ObjendC_OX = PFN_CreateFootballRobot("Surge Ping", 248, "右后卫")C_OX.SetName(C_OX, "luonaerduo")C_OX.SetPosition(C_OX, "右后卫")print("C_OX's name,id,position:", C_OX.GetName(C_OX), C_OX.GetId(C_OX), C_OX.GetPostion(C_OX))--[[函数闭包1.一个函数所使用的定义在它的函数体之外的局部变量(external local variable)称为这个函数的upvalue。在前面的代码中,函数countDown使用的定义在函数createCountdownTimer中的局部变量ms就是countDown的upvalue,但ms对createCountdownTimer而言只是一个局部变量,不是upvalue。Upvalue是Lua不同于C/C++的特有属性,需要结合代码仔细体会。2.函数闭包一个函数和它所使用的所有upvalue构成了一个函数闭包。3.Lua函数闭包与C函数的比较Lua函数闭包使函数具有保持它自己的状态的能力,从这个意义上说,可以与带静态局部变量的C函数相类比。但二者有显著的不同:对Lua来说,函数是一种基本数据类型——代表一种(可执行)对象,可以有自己的状态;但是对带静态局部变量的C函数来说,它并不是C的一种数据类型,更不会产生什么对象实例,它只是一个静态地址的符号名称。4.说明:局部函数(非全局函数)声明局部函数的两种方式:1). 方式一local f = function (...)...endlocal g = function (...)...f() -- external local `f' is visible here...end2). 方式二local function f (...)...end有一点需要注意的是在声明递归局部函数的方式:local fact = function (n)if n == 0 thenreturn 1elsereturn n*fact(n-1) --有问题的endendlocal fact--上面这种方式导致Lua编译时遇到fact(n-1)并不知道他是局部函数fact,Lua会去查找是否有这样的全局函数fact。为了解决这个问题我们必须在定义函数以前先声明:fact = function (n)if n == 0 thenreturn 1elsereturn n*fact(n-1)endend这样在fact内部fact(n-1)调用是一个局部函数调用,运行时fact就可以获取正确的值了。--]]function PFN_CreateCountdownTimer(seconds)local ms = seconds * 1000--局部变量ms,就是PFN_CountDown函数的upvaluelocal function PFN_CountDown()--局部函数ms = ms - 1return msendreturn PFN_CountDownendprint("------------函数闭包")timer1 = PFN_CreateCountdownTimer(1)for i = 1, 3 doprint(timer1())endprint("------")timer2 = PFN_CreateCountdownTimer(1)for i = 1, 3 doprint(timer2())end--[[基于对象的实现方式1.实现方式把需要隐藏的成员放在一张表里,把该表作为成员函数的upvalue。2.局限性基于对象的实现不涉及继承及多态。但另一方面,脚本编程是否需要继承和多态要视情况而定。--]]function PFN_Create(name, id)local data = {m_name = name, m_id = id}--把需要隐藏的数据成员m_name,m_id放入data表中local c_obj = {}function c_obj:SetName(name)data.m_name = nameendfunction c_obj:GetName()return data.m_nameendfunction c_obj:SetId(id)data.m_id = idendfunction c_obj:GetId()return data.m_idendreturn c_objendc_o1 = PFN_Create("Suregeping", 6565)c_o2 = PFN_Create("hello".. ",Jim", 007)c_o1.SetName(c_o1, "welcome")--c_o2.SetName(c_o2, "Jim")print("c_o1's name,id:", c_o1.GetName(c_O1), c_o1.GetId(c_o1))print("c_o2's name,id:", c_o2.GetName(c_o2), c_o2.GetId(c_o2))--[[元表Lua 中的每个值都可以用一个 metatable。这个 metatable 就是一个原始的Lua table ,它用来定义原始值在特定操作下的行为。你可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值的指定操作之行为。举例来说,当一个非数字的值作加法操作的时候, Lua 会检查它的 metatable 中 "__add" 域中的是否有一个函数。如果有这么一个函数的话,Lua 调用这个函数来执行一次加法。可以通过 getmetatable 函数来查询到任何一个值的 metatable。你可以通过 setmetatable 函数来替换掉 table 的 metatable每个 table 和 userdata都可以拥有独立的 metatable(当然多个 table 和userdata可以共享一个相同的表作它们的metatable);其它所有类型的值,每种类型都分别共享唯一的一个 metatable。因此,所有的数字一起只有一个 metatable ,所有的字符串也是,等等。元表的键名称为 事件,其中的值(换而言之就是函数)称为元方法。一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为,    metatable 可以控制的操作已在下面列出来。每个操作都用相应的名字区分。每个操作的键名都是用操作名字加上两个下划线 '__' 前缀的字符串;举例来说,"add" 操作的键名就是字符串 "__add"。这些操作的语义用一个 Lua 函数来描述解释器如何执行更为恰当。1.定义元表本身只是一个普通的表,通过特定的方法(比如setmetatable)设置到某个对象上,进而影响这个对象的行为;一个对象有哪些行为受到元表影响以及这些行为按照何种方式受到影响是受Lua语言约束的。比如在前面的代码里,两个表对象的加法运算,如果没有元表的干预,就是一种错误;但是Lua规定了元表可以“重载”对象的加法运算符,因此若把定义了加法运算的元表设置到那两个表上,它们就可以做加法了。元表是Lua最关键的概念之一,内容也很丰富,请参考Lua文档了解详情。2.元表与C++虚表的比较如果把表比作对象,元表就是可以改变对象行为的“元”对象。在某种程度上,元表可以与C++的虚表做一类比。但二者还是迥然不同的:元表可以动态的改变,C++虚表是静态不变的;元表可以影响表(以及其他类型的对象)的很多方面的行为,虚表主要是为了定位对象的虚方法(最多再带上一点点RTTI)__concat连接操作,__add加法操作(其他算法运算类似),取下标操作(用于访问table[key])__index,比较操作,如等于__eq,小于__lt,小于等于__le等,取长度操作__len--]]print("---------------元表01")t = {}m = {a = "and", b = "Li Lei", c = "Han Meimei"}setmetatable(t, {__index = m}) --表{__index = m}作为表t的元表for k, v in pairs(t) do --穷举表print(k, v)--其实没数据endprint("--------")print(t.b, t.a, t.c)print("----------------元表02")function add(t1, t2)assert(#t1 == #t2)--运算符#取表长度local length = #t1fori = 1, length dot1[i] = t1[i] + t2[i]endreturn t1endt1 = setmetatable({1, 2, 3}, {__add = add})--setmetatable返回被设置的表t2 = setmetatable({10, 20, 30}, {__add = add})t1 = t1 + t2for i = 1, #t1 doprint(t1[i])end--[[基于原型的继承1.prototype模式一个对象既是一个普通的对象,同时也可以作为创建其他对象的原型的对象(即类对象,class object);动态的改变原型对象的属性就可以动态的影响所有基于此原型的对象;另外,基于一个原型被创建出来的对象可以重载任何属于这个原型对象的方法、属性而不影响原型对象;同时,基于原型被创建出来的对象还可以作为原型来创建其他对象。--]]print("-------------------基于原型的继承")Robot = {m_name = "Sam", m_id = 001}function Robot:PFN_New(extension)local t = setmetatable(extension or {}, self)self.__index = selfreturn tendfunction Robot:PFN_SetName(name)self.m_name = nameendfunction Robot:PFN_GetName()return self.m_nameendfunction Robot:PFN_SetId(id)self.m_id = idendfunction Robot:PFN_GetId()return self.m_idendrobot = Robot:PFN_New()print("Robot's name:", Robot:PFN_GetName())print("Robot's id:", Robot:PFN_GetId())print("-------------")FootBallRobot = Robot:PFN_New({m_position = "right back"})function FootBallRobot:PFN_SetPosition(p)self.m_position = pendfunction FootBallRobot:PFN_GetPosition()return self.m_positionendfbr = FootBallRobot:PFN_New()print("fbr's position:", FootBallRobot:PFN_GetPosition())print("fbr's name:", FootBallRobot:PFN_GetName())print("fbr's id:", FootBallRobot:PFN_GetId())print("-------------")fbr:PFN_SetName("Surge Ping")print("fbr's name:", fbr:PFN_GetName())print("Robot's Name:", Robot:PFN_GetName())--[[函数环境1.定义函数环境就是函数在执行时所见的全局变量的集合,以一个表来承载。2.说明每个函数都可以有自己的环境,可以通过setfenv来显示的指定一个函数的环境。如果不显示的指定,函数的环境缺省为定义该函数的函数的环境。类型为 thread ,function ,以及 userdata 的对象,除了 metatable元表外,还可以用另外一个与之关联的被称作它们的环境的一个表,像 metatable 一样,环境也是一个常规的 table ,多个对象可以共享同一个环境。关联在线程上的环境被称作全局环境。全局环境被用作它其中的线程以及线程创建的非嵌套函数(通过 loadfile , loadstring 或是 load )的缺省环境。关联在 C 函数上的环境可以直接被 C 代码访问(参见 §3.3)。它们会作为这个 C 函数中创建的其它函数的缺省环境。关联在 Lua 函数上的环境用来接管在函数内对全局变量的所有访问。它们也会作为这个函数内创建的其它函数的缺省环境。可以通过调用 setfenv 来改变一个 Lua函数或是正在运行中的线程的环境。而想操控其它对象(userdata、C函数、其它线程)的环境的话,就必须使用 C API 。在前面的代码中,函数foo的缺省环境里没有定义变量g,因此第一次执行foo, g为nil,表达式g or "No g defined!"的值就是"No g defined!"。随后,foo被指定了一个环境 { g = 100, print = print }。这个环境定义了(全局)变量g,以及打印函数print,因此第二次执行foo,g的值就是100。但是在定义函数foo的函数的环境下,g仍然是一个未定义的变量。3.应用函数环境的作用很多,利用它可以实现函数执行的“安全沙箱”;另外Lua的包的实现也依赖它。--]]print("---------------函数环境")function foo()print(g or "No g defined!")endfoo()setfenv(foo, {g = 100, print = print})--设置foo的环境为表{g=100,...}foo()print(g or "No g defined!")--[[包1.使用方式       一般用require函数来导入一个包,要导入的包必须被置于包路径(packagepath)上。包路径可以通过package.path或者环境变量来设定。一般来说,当前工作路径总是在包路径中。--]]print("----------------包的使用")pack = require("mypack")--导入包print(ver or "No ver defined!")print(pack.ver)print(PFN_FunInMyPack or "No PFN_FunInMyPack defined!")pack.PFN_FunInMyPack()print(PFN_FunFromMyPack or "No PFN_FunFromMyPack defined!")PFN_FunFromMyPack()--[[高阶话题: 迭代1.定义迭代是for语句的一种特殊形式,可以通过for语句驱动迭代函数对一个给定集合进行遍历。正式、完备的语法说明较复杂,请参考Lua手册。2.实现如前面代码所示:enum函数返回一个匿名的迭代函数,for语句每次调用该迭代函数都得到一个值(通过element变量引用),若该值为nil,则for循环结束。3.说明用范型for循环,也可以对一个给定的集合进行遍历;这两种方式都适合遍历一个不清楚集合长度的遍历如days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}for k,v in pairs(days) doprint(v)end--]]print("-----------------迭代(for循环迭代器)")function PFN_Enum(array)local index = 1return function()local ret = array[index]index = index + 1return retendendfunction PFN_Foreach(array, action)for element in PFN_Enum(array) doaction(element)endendPFN_Foreach({1, 2, 3}, print)--[[协作线程coroutine1.创建协作线程通过coroutine.create可以创建一个协作线程,该函数接收一个函数类型的参数作为线程的执行体,返回一个线程对象。也可以通过coroutine.wrap来创建一个协作线程,该函数接收一个函数类型的参数作为线程的执行体,只是它的返回值是一个函数。2.启动线程通过coroutine.resume可以启动一个线程或者继续一个挂起的线程。该函数接收一个线程对象以及其他需要传递给该线程的参数。线程可以通过线程函数的参数或者coroutine.yield调用的返回值来获取这些参数。当线程初次执行时,resume传递的参数通过线程函数的参数传递给线程,线程从线程函数开始执行;当线程由挂起转为执行时,resume传递的参数以yield调用返回值的形式传递给线程,线程从yield调用后继续执行。3.线程放弃调度线程调用coroutine.yield暂停自己的执行,并把执行权返回给启动/继续它的线程;线程还可利用yield返回一些值给后者,这些值以resume调用的返回值的形式返回。注意:协作线程,简称协程。协程与VC中的线程一样,只是它是同一时刻只有一个在运行,是串行的,不是并行的。多线程中宏观意义上来说 ,是多个线程同时运行。所以这样的话,协程并不能体现出线程的并发优势。--]]print("-----------协作线程的创建和启动")function PFN_Procducer()--创建线程和线程处理函数return coroutine.create(function(salt)local t = {1, 2, 3}for i = 1,#t dosalt = coroutine.yield(t[i] + salt)--显示的调用yield函数时,线程会被挂起,且通过yield函数将其一些参数作为结果传递给resumeendend)endfunction PFN_Consumer(prod)--启动线程local salt = 10while true dolocal running, product = coroutine.resume(prod, salt)--resume函数是启动一个线程或继续执行一个被挂起的线程。当第一次resume线程时,其一些参数是作为线程处理函数的参数被传递的,若线程已经创建了,则参数是被作为来自yield函数的结果传递过来的--返回值:如果协作线程执行没有错误,resume则返回true,和一些参数被传递到yield或一些值从线程处理函数中返回;如果有错误resume返回false和错误信息。salt = salt * saltif running thenprint(product or "END!")elsebreakendendendPFN_Consumer(PFN_Procducer())--协作线程02print("-----------------协作线程02\n利用协作线程可以方便地设计出类似Unix管道或者Stream IO的结构-----------")function instream()return coroutine.wrap(function()while true dolocal line = io.read("*l")if line thencoroutine.yield(line)elsebreakendendend)endfunction filter(ins)return coroutine.wrap(function()while true dolocal line = ins()if line thenline = "**"..line.."**"coroutine.yield(line)elsebreakendendend)endfunction outstream(ins)while true dolocal line = ins()if line thenprint(line)elsebreakendendendoutstream(filter(instream()))--协作线程03--[[另一种迭代方式协作线程可以作为for循环迭代器的另一种实现方式。虽然对于简单的数组遍历来说,没有必要这么做,但是考虑一下,如果需要遍历的数据集合是一个复杂数据结构,比如一棵树,那么协作线程在简化实现上就大有用武之地了。--]]print("--------------协作线程03\n协作线程可以作为for循环迭代器的另一种实现方式")function PFN_Enum(array)return coroutine.wrap(function()local len = #arrayfor i = 1, len docoroutine.yield(array[i])endend)endfunction PFN_Foreach(array, action)for element in PFN_Enum(array) doaction(element)endendPFN_Foreach({1, 2, 3}, print)