C/C++程序员的Lua快速入门指南RobertZ2010-1前言本文针对的读者是有经验的C/C++程序员,希望了解Lua或者迅速抓住Lua的关键概念和模式进行开发的。因此本文并不打算教给读者条件语句的语法或者函数定义的方式等等显而易见的东西,以及一些诸如变量、函数等编程语言的基本概念。本文只打算告诉读者Lua那些与C/C++显著不同的东西以及它们实际上带来了怎样不同于C/C++的思考方式。不要小看它们,它们即将颠覆你传统的C/C++的世界观!本文一共分初阶、进阶和高阶三大部分,每个部分又有若干章节。读者应当从头至尾循序渐进的阅读,但是标有“*”号的章节(主要讨论OO在Lua中的实现方式)可以略去而不影响对后面内容的理解。读者只要把前两部分完成就可以胜任Lua开发的绝大部分任务。高阶部分可作为选择。本文不打算取代Lua参考手册,因此对一些重要的Lua函数也未做足够的说明。在阅读的同时或者之后,读者应当在实践中多多参考Lua的正式文档(附录里列出了一些常用的Lua参考资料)。请访问本文的在线版本获得最新更新。另外,作者还有一个开源的Lua调试器——RLdb以及一个讨论Lua的站点,欢迎访问。欢迎读者来信反馈意见。初阶话题数据类型函数表简单对象的实现*简单继承*数据类型八种基本类型:数值(number)内部以double表示字符串(string)总是以零结尾,但可以包含任意字符(包括零),因此并不等价于C字符串,而是其超集。布尔(boolean)只有“true”或者“false”两个值。函数(function)Lua的关键概念之一。不简单等同于C的函数或函数指针。表(table)异构的Hash表。Lua的关键概念之一。userdata用户(非脚本用户)定义的C数据结构。脚本用户只能使用它,不能定义。线程(thread)Lua协作线程(coroutine),与一般操作系统的抢占式线程不一样。nil代表什么也没有,可以与C的NULL作类比,但它不是空指针。函数functionfoo(a,b,c)localsum=a+breturnsum,c--函数可以返回多个值endr1,r2=foo(1,'123','hello')--平行赋值print(r1,r2)输出结果:124hello函数(续)函数定义用关键字function定义函数,以关键字end结束局部变量用关键字local定义。如果没有用local定义,即使在函数内部定义的变量也是全局变量!函数可以返回多个值returna,b,c,...平行赋值a,b=c,d全局变量前面的代码定义了三个全局变量:foo、r1和r2表a={}b={x=1,[hello,]=world!}a.astring=ni,hao!a[1]=100a[atable]=bfunctionfoo()endfunctionbar()enda[foo]=bar--分别穷举表a和bfork,vinpairs(a)doprint(k,=,v)endprint(----------------------------)fork,vinpairs(b)doprint(k,=,v)end输出结果:1=100atable=table:003D7238astring=ni,hao!function:003DBCE0=function:003DBD00----------------------------hello,=world!x=1表定义表(Table)的方式a={},b={…}访问表的成员通过“.”或者“[]”运算符来访问表的成员。注意:表达式a.b等价于a[“b”],但不等价于a[b]表项的键和值任何类型的变量,除了nil,都可以做为表项的键。从简单的数值、字符串到复杂的函数、表等等都可以;同样,任何类型的变量,除了nil,都可以作为表项的值。给一个表项的值赋nil意味着从表中删除这一项,比如令a.b=nil,则把表a中键为“b”的项删除。如果访问一个不存在的表项,其值也是nil,比如有c=a.b,但表a中没有键为“b”的项,则c等于nil。一种简单的对象实现方式*functioncreate(name,id)localobj={name=name,id=id}functionobj:SetName(name)self.name=nameendfunctionobj:GetName()returnself.nameendfunctionobj:SetId(id)self.id=idendfunctionobj:GetId()returnself.idendreturnobjendo1=create(Sam,001)print(o1'sname:,o1:GetName(),o1'sid:,o1:GetId())o1:SetId(100)o1:SetName(Lucy)print(o1'sname:,o1:GetName(),o1'sid:,o1:GetId())输出结果:o1'sname:Samo1'sid:1o1'sname:Lucyo1'sid:100一种简单的对象实现方式*(续)对象工厂模式如前面代码的create函数用表来表示对象把对象的数据和方法都放在一张表内,虽然没有隐藏私有成员,但对于简单脚本来说完全可以接受。成员方法的定义functionobj:method(a1,a2,...)…end等价于functionobj.method(self,a1,a2,...)…end等价于obj.method=function(self,a1,a2,...)…end成员方法的调用obj:method(a1,a2,…)等价于obj.method(obj,a1,a2,...)简单继承*functioncreateRobot(name,id)localobj={name=name,id=id}functionobj:SetName(name)self.name=nameendfunctionobj:GetName()returnself.nameendfunctionobj:GetId()returnself.idendreturnobjendfunctioncreateFootballRobot(name,id,position)localobj=createRobot(name,id)obj.position=rightbackfunctionobj:SetPosition(p)self.position=pendfunctionobj:GetPosition()returnself.positionendreturnobjend简单继承*(续)优点:简单、直观缺点:传统、不够动态进阶话题函数闭包(functionclosure)基于对象的实现方式(objectbasedprogramming)*元表(metatable)基于原型的继承(prototypebasedinheritance)*函数环境(functionenvrionment)包(package)函数闭包functioncreateCountdownTimer(second)localms=second*1000localfunctioncountDown()ms=ms-1returnmsendreturncountDownendtimer1=createCountdownTimer(1)fori=1,3doprint(timer1())endprint(------------)timer2=createCountdownTimer(1)fori=1,3doprint(timer2())end输出结果:999998997------------999998997函数闭包(续)Upvalue一个函数所使用的定义在它的函数体之外的局部变量(externallocalvariable)称为这个函数的upvalue。在前面的代码中,函数countDown使用的定义在函数createCountdownTimer中的局部变量ms就是countDown的upvalue,但ms对createCountdownTimer而言只是一个局部变量,不是upvalue。Upvalue是Lua不同于C/C++的特有属性,需要结合代码仔细体会。函数闭包一个函数和它所使用的所有upvalue构成了一个函数闭包。Lua函数闭包与C函数的比较Lua函数闭包使函数具有保持它自己的状态的能力,从这个意义上说,可以与带静态局部变量的C函数相类比。但二者有显著的不同:对Lua来说,函数是一种基本数据类型——代表一种(可执行)对象,可以有自己的状态;但是对带静态局部变量的C函数来说,它并不是C的一种数据类型,更不会产生什么对象实例,它只是一个静态地址的符号名称。基于对象的实现方式*functioncreate(name,id)localdata={name=name,id=id}localobj={}functionobj.SetName(name)data.name=nameendfunctionobj.GetName()returndata.nameendfunctionobj.SetId(id)data.id=idendfunctionobj.GetId()returndata.idendreturnobjendo1=create(Sam,001)o2=create(Bob,007)o1.SetId(100)print(o1'sid:,o1.GetId(),o2'sid:,o2.GetId())o2.SetName(Lucy)print(o1'sname:,o1.GetName(),o2'sname:,o2.GetName())输出结果:o1'sid:100o2'sid:7o1'sname:Samo2'sname:Lucy基于对象的实现方式*(续)实现方式把需要隐藏的成员放在一张表里,把该表作为成员函数的upvalue。局限性基于对象的实现不涉及继承及多态。但另一方面,脚本编程是否需要继承和多态要视情况而定。元表t={}m={a=and,b=LiLei,c=HanMeimei}setmetatable(t,{__index=m})--表{__index=m}作为表t的元表fork,vinpairs(t)do--穷举表tprint(k,v)endprint(-------------)print(t.b,t.a,t.c)输出结果:-------------LiLeiandHanMeimei元表(续)functionadd(t1,t2)--‘#’运算符取表长度assert(#t1==#t2)locallength=#t1fori=1,lengthdot1[i]=t1[i]+t2[i]endreturnt1end--setmetatable返回被设置的表t1=setmetatable({1,2,3},{__add=add})t2=setmetatable({10,20,30},{__add=add})t1=t1+t2fori=1,#t1doprint(t1[i])end输出结果:112233元表(续)定义元表本身只是一个普通的表,通过特定的方法(比如setmetatable)设置到某个对象上,进而影响这个对象的行为;一个对象有哪些行为受到元表影响以及这些行为按照何种方式受到影响是受Lua语言约束的。比如在前面的代码里,两个表对象的加法运算,如果没有元表的干预,就是一种错误;但是Lua规定了元表可以“重载”对象的加法运算符,因此若把定义了加法运算的元表设置到那两个表上,它们就可以做加法了。元表是Lua最关键的概念之一,内容也很丰富,请参考Lua文档了解详情。元表与C++虚表的比较如果把表比作对象,元表就是可以改变对象行为的“元”对象。在某种程度上,元表可以与C++的虚表做一类比。但二者还是迥然不同的:元表可以动态的改变,C++虚表是静态不变的;元表可以影响表(以及其他类型的对象)