lua入门学习笔记(1)
以下是lua的一些基础知识的介绍,文章所附的代码可以直接拷贝运行,如有错误,请留言指正。
第一部分 初阶话题
1,注释
行注释:以两个连字符"--"开始一个行注释。
块注释:以"--[["开始,以"--]]"结束。
2,八种数据类型
数值(number):内部以double表示的数值
字符串(string):由任意字符(包括0)组成的,以0结尾的字符序列
布尔(boolean):只有"true"和"false"两个值的类型
函数(function):基本的lua对象,不同于C语言的函数和函数指针,lua的关键概念之一
表(table): "异构"的hash表,lua的另一关键概念
自定义类型(userdata):C语言用户定义的数据结构,用于扩展Lua语言,本文不会涉及
线程(thread):协作线程(coroutine),不同于一般操作系统的抢占式线程
nil:代表"空",类似于C语言的NULL,但实际含义要深刻的多
3,函数
函数定义
用关键字function定义函数,以关键字end结束
函数可以返回多个值
return a, b, c, ...
平行赋值
a, b = c, d
function foo(a, b, c) local sum = a + b return sum, c --函数可以返回多个值 end r1, r2 = foo(1, '123', 'hello') --平行赋值 print(r1, r2) -->124 hello
局部变量与全局变量:
局部变量用关键字local定义。如果没有用local定义,即使在函数内部定义的变量也是全局变量!
没有用local定义的变量都是全局变量。前面的代码定义了三个全局变量:foo、r1和r2
4,表
Lua的表既是Hash表,也是数组。实际上,可以把数组看作键为数值的Hash表。
访问表的成员:通过“.”或者“[]”运算符来访问表的成员。
注意:表达式a.b等价于a[“b”],但不等价于a[b]
表项的键和值:任何类型的变量,除了nil,都可以做为表项的键或值。
给一个表项的值赋nil意味着从表中删除这一项:比如令a.b = nil,则把表a中键为“b”的项删除。
访问一个不存在的表项,会得到nil:比如有c = a.b,但表a中没有键为“b”的项,则c得到nil。
--定义一张空表 a = {} --定义一张有初始内容的表 b={n=1,str='abc',100,'hello'} --访问表的成员 a.n=1 a.str='abc' a[1]=100 a[2]='hello' a["a table"] = b --任何类型的值都可以做表项的key --除了nill function func1() end function func2() end a[func1] = func2 --穷举表a,函数pairs()可以遍历table中的每一个元素。 for i,v in pairs(a) do print(i,"=>",v) end --[[ 输出: 1 => 100 2 => hello str => abc function: 0041C0E0 => function: 0041C100 a table => table: 00417420 n => 1 --]]
第二部分 进阶话题
1,函数闭包(function closure)
函数闭包:一个函数和它所有upvalue构成一个函数闭包。函数闭包是lua这一类"函数式语言"的核心概念。
Upvalue :一个函数所使用的定义在它的函数体之外的局部变量(external local variable),称为“非局部的变量”,也称为这个函数的upvalue。
示例1:
function newCounter() local i = 0 return function() --匿名函数 i = i +1 return i end end c1 = newCounter() print(c1()) -->1 print(c1()) -->2
在上面这段代码中,匿名函数访问了一个“非局部的变量”i,该变量用于保持一个计数器。初看上去,由于创建变量i的函数(newCounter)已经返回,所以之后每次调用匿名函数时,i都是已超出了作用范围的。但其实不然,lua会以closure的概念来正确地处理这种情况。简单地讲,一个closure就是一个函数加上该函数所需访问的所有“非局部的变量”即(upvalue),如果再次调用newCounter,那么它会创建一个新的局部变量i,从而也将得到一个新的closure。
c2 = newCounter() print(c2()) -->1 print(c1()) -->3 print(c2()) -->2
因为c1和c2是同一个函数所创建的两个不同的closure,它们各自拥有局部变量i独立实例。
示例2:
function createCountdownTimer(second) local ms = second * 1000 local function countDown() ms = ms - 1 return ms end return countDown end
timer1 = createCountdownTimer(1) for i = 1,3 do print(timer1())
end print("----------")
timer2 = createCountdownTimer(1) for i = 1,3 do print(timer2()) end --[[
输出:
999 998 997 ---------- 999 998 997 --]]
在上面的代码中,函数countDown使用的定义在函数createCountdownTimer中的局部变量ms就是countDown的upvalue,但ms对createCountdownTimer而言只是一个局部变量,不是upvalue.
lua函数闭包与C函数的比较: lua函数闭包使函数在几次调用间具有保持自身状态的能力,从此角度看,与带静态局部变量的C函数相似。但二者其实截然不同:前者是一个运行时对象,后者只是一个静态地址;前者可以有“同一类型”的若干实例,每个实例都有自己的状态,而后者只是一个静态地址,谈不上实例化。
2,基于对象的编程(object based programming)
实现要点: 把需要隐藏的成员放在一张表里,把该表作为公有成员函数的upvalue;再把所有的共有成员放在另一张表里,把这张表作为对象。
局限性: 考虑到对象继承的情况,这种方法的适用性有所限制。但另一方面,是否需要对象继承要视情况而定。
function createFoo(name) local data = {name = name} local obj = {} function obj.SetName(name) data.name = name end function obj.GetName() return data.name end return obj end
o = createFoo("Sam") print("name:",o.GetName()) -->name: Samo.SetName("Lucy") print("name:",o.GetName()) -->name: Lucy
3,元表(metatable)
元表本身只是一张普通的表,一般带有一些特殊的事件回调函数,通过setmetatable被设置到某个对象上进而影响这个对象的行为。回调事件(如__index和__add)由lua定义,而事件回调函数由脚本用户定义并在相应事件发生时被lua解释器调用。以下面的代码为例,表的加法运算在缺省状态下将产生异常,但是设置了适当元表的表就可以进行加法运算了——lua解释器将在表做加法运算时调用用户定义的__add回调函数。
重载运算符: lua里运算符可以被重载,不仅是“+”运算,几乎所有的对象的运算符都可以被重载。
t={} t2={a="and",b="Li Lei",c= "HanMeiMei"} m = {__index = t2} setmetatable(t,m) --设表m为表t的元表 for k,v in pairs(t) do --穷举表t print(k,v) end print("----------") print(t.b,t.a,t.c) --[[ 输出结果: ---------- Li Lei and HanMeiMei --]]
function add(t1,t2) local length = #t1 --'#'运算符取表长度 for i = 1,length do t1[i]= t1[i] + t2[i] end return t1 end t1 = {1,2,3} t2 = {10,20,30} setmetatable(t1,{__add = add}) setmetatable(t2,{__add = add}) t1 = t1 + t2 --穷举表t1 for i = 1,#t1 do print(t1[i]) end --[[ 输出结果: 11 22 33 --]]
4,基于原型(prototype)的继承
一个对象既是一个普通的对象,同时也可以作为创建其他对象的原型的对象(即类对象,class object);动态的改变原型对象的属性就可以动态的影响所有基于此原型的对象;另外,基于一个原型被创建出来的对象可以重载任何属于这个原型对象的方法、属性而不影响原型对象;同时,基于原型被创建出来的对象还可以作为原型来创建其他对象。
Robot = {name = "Sam",id=001} function Robot:New(extension) local t = setmetatable(extension or {},self) self.__index = self return t end function Robot:SetName(name) self.name = name end function Robot:GetName() return self.name end function Robot:SetId(id) self.id = id end function Robot:GetId() return self.id end robot = Robot:New() print("robot's name: ",robot:GetName()) print("robot's id: ", robot:GetId()) print("---------------") FootballRobot = Robot:New({position = "right back"}) function FootballRobot:SetPosition(p) self.position = p end function FootballRobot:GetPosition(p) return self.position end fr = FootballRobot:New() print("fr's position:",fr:GetPosition()) print("fr's name: ",fr:GetName()) print("fr's id: ",fr:GetId()) print("---------------") fr:SetName("Bob") fr:SetId(002) print("fr's name: ",fr:GetName()) print("fr's id: ", fr:GetId()) print("---------------") Robot:SetId(003) print("fr's name: ",fr:GetId()) print("robot's id: ",robot:GetId()) --[[ 输出结果: robot's name: Sam robot's id: 1 --------------- fr's position: right back fr's name: Sam fr's id: 1 --------------- fr's name: Bob fr's id: 2 --------------- fr's name: 2 robot's id: 3 --]]
5,模块
模块是代码的一种组织方式。
定义模块,一般在一个lua文件内以module调用开始定义一个模块。module调用同时为这个lua文件定义了一个新的函数环境(初始为空表)。
--定义名为hi的模块,并使全局变量在此模块内可见 module('hi',package.seeall) ver = "version 0.1" function Hello() print("hello!") end --使用该模块 require "hi" print(hi.ver) hi.Hello()
--[[ 输出结果: version 0.1 hello! --]]
参考资料:
1,《lua快速入门》by RobertRay
2,《Lua程序设计》by Roberto