【Lua篇】《Lua程序设计》全书内容总结 |
您所在的位置:网站首页 › lua语言编程 › 【Lua篇】《Lua程序设计》全书内容总结 |
这篇文章是 《Lua程序设计》 的读书笔记和概要。这是关于lua编程最权威的书籍之一。推荐给lua基础不够牢的童鞋。没有看过的可以通过我这篇文章快速浏览书中内容,已经看过的可以也能借助这篇文章复习一遍。另外由于我之前是使用c#的,所以这篇文章也会提到c#和lua的一些不同点。 全书内容分为4个部分: 第1章到第10章,讲了基础数据类型、函数、闭包、协程、错误处理。这一部分的内容属于最基础的语法,只要学过其它语言,看这部分毫无压力。 第11章到第17章,全书最重要的部分。讲了表、元表元方法、环境、模块与包、面向对象、弱引用。这部分内容属于lua的高级功能,其中的表和元表我认为是lua最独特的地方。 第18章到第23章, 讲了数学库、Table库、字符串库、IO库、操作系统库、Debug库。属于lua标准库里的内容,大部分都在讲库函数如何使用,只需要做了解即可。里面最有用的个人认为是数学库、Table库、Debug库。其它的用到再查就可以了。 第24章到第31章,讲了CAPI、从Lua调C、从C调Lua、给Lua扩展userdata、内存管理。这部分属于其它语言和lua之间交互的内容。如果需要给项目接入lua或者想看懂xlua、tolua这种lua插件,那么这部分内容也是必看的。程序块,即chunk,由一行或多行lua可执行的代码构成。下面两段代码,一个是程序块一个不是。 -- 是程序块 function f(a,b) return a*a - b*b; end -- 不是程序块 do return 1;一般使用dofile或require来执行程序块。 1.2 词法规范lua变量不能以数字开头,也要避免以下划线开头。lua中的一些特殊变量就是以下划线开头的。也不要使用and、break、do、else、elseif等关键字做变量。 1.3 全局变量不加local的变量就是全局变量,不需要全局变量了把它设为nil即可。 1.4解释器程序讲了在UNIX系统中怎么把lua当成脚本解释器来用。 第2章 类型与值2.1 nil(空)nil主要功能在于区别其他任何值。 2.2 boolean(布尔)注意boolean不是一个条件值的唯一表示方式。false和nil都是假,其他情况都是真。 2.3 number(数字)lua没有整数类型,都是使用双精度浮点数。 2.4 string(字符串)字符串机制和c#类型,修改字符串变量时,是创建了一个新的字符串。字符串和数字之间可以自动转换。 lua中使用 两个点来连接字符串。如: print(10..20)2.5 table(表)table和c#的字典有点类似,可以通过索引找到对应的值。除了数组和字典,lua的表还可以用来表示队列、对象等数据结构。不仅可以通过整数来索引,还可以通过字符串或者其他类型(除了nil)。可以使用table.key,table[key]来访问值。注意下面写法上的不同: a = {} x = "y" a[x] = 10 -- 结果为10 print(a[x]) -- 结果为nil print(a.x) -- 结果为10 print(a.y)2.6 function(函数)lua既可以调用lua编写的函数,也能调用以c编写的函数。第5章、第26章将详细讨论。 2.7 userdata(自定义类型)和thread(线程)在其他语言编写的数据类型传到lua里面就是自定义类型,如unity里的transform、component等。线程会在第9章解释。 第3章 表达式3.1 算术操作符算术操作符和其他编程语言差别不大,除了加减乘除指数 +、-、*、/、^ 外还有 % 取模。当%用于非整数时表示小数点后几位的数据。比如x%1结果就是x的小数部分,x%0.01表示小数点后2位以后的数。x-x%0.01表示x精确到小数点后两位的结果。 3.2 关系操作符< > = == ~= 对于table,userdata,函数使用引用比较。 3.3 逻辑操作符逻辑操作符有and、or、not。and和or都有短路求值。即第一个操作数满足了才会评估第二个操作数。 3.4 字符串连接上面已经提到了字符串相关知识,这里不再赘述。 3.5 优先级记住操作符优先级并没有什么意义,使用括号指定运算顺序即可。 3.6 table构造式使用以下语法构造一个table: a = { x = 10, y =20} a = {} a = {10,20}在没有指定key时,会自动使用数字进行索引,并且以1开头。 第4章 语句4.1 赋值lua允许多重赋值。例如: a,b = 10, 20 -- 交换x与y x,y = y,x多重赋值还能用来接收函数的多个返回值: a,b = f()4.2 局部变量与块lua中使用local创建局部变量,局部变量会随着作用域的结束而消失。在lua中,有一种推荐写法: local foo = foo这句代码创建了局部变量foo,并把全局变量foo赋值给它。这种写法两个好处: - 如果后面代码修改了全局foo,也能拿到修改前的值 - 加速当前作用域对foo的访问 4.3 控制结构if then else endwhile dorepeat until数字型for for i=1,10 do print(i) end泛型for。ipair按照索引升序遍历,当key不是整数时返回,当key不等于索引时返回 for i,v in ipairs(a) do print(v) end4.4 break与returnbreak与return用于跳出当前的块。 第5章 函数函数的冒号操作符调用table的函数时将table隐含地作为函数的第一个参数。o.foo(o,x)另一种写法是o:foo(x) 。 5.1 多重返回值lua中的函数可以有多个返回值,并且会根据不同的调用情况选择舍弃返回值。 function foo() retuan "a","b" end -- x = "a" , "b"被舍弃 x = foo() -- x = "a", y = 20 x,y = foo(),20关于多重返回值还有一个特殊函数unpack。它接受一个数组并且从索引1开始返回数组的所有元素。 -- 打印10,20,30 print(unpack(10,20,30))5.2 变长参数lua中的函数可以传入不定数量的实参。使用3个点来表示,这种参数叫作变长参数。 function foo(...) local a,b,c = ... end如果有固定参数,固定参数必须放在变长参数之前。 5.3 具名实参先来回顾一下c#的缺省参数: private void MyFunc( int a = 0 ,int b = 0 ,int c = 0) { }c#里可以不按照参数的顺序来使用函数: MyFunc( b : 10, a : 10 )lua不直接支持这种语法,但是可以通过将函数参数设为table,来实现这种机制。 function rename (arg) return os.rename(arg.old,arg.new) end rename{old = "temp.lua",new = "temp1.lua"}当实参只有一个table时,可以省略圆括号。 第6章 深入函数在lua中,函数是一种第一类值。这个概念的意思是函数和其他类型的值具有相同的权利,即可以传参,可以作为返回值。 函数和其他值一样是匿名的,一个函数定义其实是一条赋值语句。如下面两种写法是等价的: function foo(x) return 2*x end foo = function(x) return 2*x end除了第一类值, lua中函数的另一个特征是有词法域。这是指当一个函数嵌套在另一个函数时,内部函数可以访问外部函数的局部变量。这两种特征都是c#没有的,c#中只能使用委托和匿名委托来实现类似的机制。 6.1 closure(闭包)先来看以下代码: function newCounter() local i = 0 retuan function() i = i+1 return i end end这段代码中展示了访问非局部变量i 的一个匿名函数。这个函数加上这种非局部变量就叫作一个closure。 如果调用多次newCounter,会创建多个新的局部变量i。 c1 = newCounter() print(c1()) -- 1 print(c1()) --2 c2 = newCounter() print(c2()) --1 print(c1()) --3 print(c2()) --26.2 非全局的函数给table定义函数的几种写法如下: Lib = {} Lib.foo = function(x,y) return x + y end Lib = { foo = function(x,y) return x + y end } Lib = {} function Lib.foo(x.y) return x + y end另外也可以给函数加上local将其定义为局部函数。 还有定义递归的局部函数时需要注意写法: 这是错误写法,这种写法其实是调用了全局的函数fact local fact = function(n) if n == 0 then return 1 else return n * fact(n-1) end end这是正确写法 local fact fact = function(n) if n == 0 then return 1 else return n * fact(n-1) end end这也是正确写法 local function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end6.3 正确的尾调用尾调用就是指一个函数的最后一步是return另一个函数。 如下就是一个尾调用 function f(x) return g(x) end而下面代码不是尾调用 function f(x) g(x) end出现尾调用后,程序不会保存尾调用所在的函数的栈信息,因为没有必要。这种现象称为尾调用消除。基于这种机制,尾调用永远不会导致栈溢出。 尾调用的典型应用就是状态机。用一个函数来表示一个状态。 第7章 迭代器和泛型for7.1 迭代器和closure在讲系统自带的迭代器函数前,先来看一下我们自己应该如何手写迭代器函数: function values(t) local i = 0 return function() i = i + 1; return t[i] end end t = {10,20,30} for element in values(t) do print(element) endfor循环会在values(t)返回的nil时停止。迭代器函数就是利用闭包的可以访问非局部变量的特性实现的。 7.2 泛型for的语义使用for in do 写法的for循环是泛型for。 ipairs、pairs 这两个东西本质上就是一个系统帮我们写好的迭代器函数。 for k,v in ipairs(t) do print(k,v) end7.3 无状态的迭代器7.1的例子有一个缺陷,就是在函数value里每次都会返回一个新的闭包。泛型for在in后面的表达式实际上可以返回3个值。分别是迭代器函数、一个恒定状态值、一个控制变量。这样的话就能保存迭代器函数了。 先来看ipirs的实现原理: local function iter(a,i) i = i + 1 local v = a[i] if v then return i,v end end function ipirs(a) return iter,a,0 enditer是迭代器函数,a是恒定状态值,0是控制变量初值。for循环的执行逻辑是调用迭代器函数,并且把恒定状态值和控制变量传入。 第一次循环是iter(a,0),第二次循环是iter(a,1),以此类推。 而函数pairs的实现原理: function pairs(t) return next,t,nil end而像ipirs,pairs这种,迭代器函数内部不保存状态,每次迭代时根据恒定状态值和控制变量决定下次的元素的的迭代器叫做无状态的迭代器。 7.4 具有复杂状态的迭代器与上面相对的,就是迭代器内部保存状态的复杂迭代器 。使用lua的闭包特性可以轻易做到这点。 7.5 真正的迭代器这一节书上展示了不使用for如何进行迭代。这个知识对于for语句的老版本lua有用。对如今的开发意义不大。 第8章 编译、执行与错误8.1 编译loadfile和dofile都是从一个文件加载lua代码块,但是不会运行它们。 区别在于loadfile不会引发错误,如果出错会返回nil。 loadstring和loadfile类似,不同在于它是从字符串读取代码。 f = loadstring("i = i + 1") f = function() i = i + 1 end这两行代码是等价的,区别在于loadstring开销更大。还有loadstring不涉及词法域。就是说它不能读取局部变量。 8.2 C代码lua可以通过动态链接调用c里面的代码。核心函数是package.loadlib。需要两个参数,动态库的路径和函数名称。案例: local path = '/usr/local/lib/lua/5.1/socket.so" local f = package.loadlib(path, "luaopen_socket")8.3 错误(error)lua内部提供了assert函数来帮助检查错误。第一个参数是bool,第二个参数是可选的字符串。 n = io.read() assert(tonumber(n),"invalid input:"..n.."is not a number")8.4 错误处理和异常如果要在lua处理错误,使用pcall来包装代码。类似c#的try、catch。 if pcall(foo) then -- 如果没有错误 else -- 如果有错误 end8.5 错误消息和追溯在遇到错误时,lua会显示错误信息,可以通过给error函数传入字符串来更改错误信息: local status,err = pcall(function() a = "a" + 1 end) print(err) -- stdin:1:attempt to perform arithmetic on a string value local status,err = pcall(function() error("my error") end) print(err) -- stdin:1:my errorerror函数还可以传入第二个参数表示由调用层级的哪一层来报告错误。 pcall有一个不足:在返回错误消息的时候,pcall到错误消息的这部分调用栈已经被销毁了。 所以lua提供了xpcall。可以传入第二个参数即一个错误处理函数。在这个函数里面可以使用debug.traceback来得到完整调用栈。 第9章 协同程序(coroutine)9.1 协同程序基础使用coroutine.create创建一个协程,返回值为thread类型: co = coroutine.create(function() print("hi") end)协程有四种状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)。 前三个都好理解。协程刚create或者内部调用yield 都会把协程变成挂起状态。代码全部执行完了就是死亡。 当协程A唤醒协程B的时候,A的状态就是正常。 使用coroutine.status来获得状态。使用coroutine.yield可以挂起协程,还能传入值作为协程返回值。 使用coroutine.resume来启动或恢复协程的执行,将其状态由挂起改为运行。 返回2个值,第一个是true或者false表示协程是否运行正常,第2个返回值是yield的值。co = coroutine.create(function(a,b) coroutine.yield(a + b, a - b) end) print(coroutine.resume(co,20,10)) --> true 30 10书中还提到了lua的协程是一种非对称协程。非对称协程在挂起时会把控制权给它的调用者。而对称式协程在挂起时会把控制权给其它对称式协程。 9.2 管道(pipe)与过滤器(filter)这一节举了一个消费者和生产者的例子来说明协程的使用,并且和UNIX的pipe进行了比较,感兴趣的自己看书。 9.3 以协同程序实现迭代器这一节用协程实现了一个迭代器,打印全排列。输出全排列是一道经典的递归算法题,所以把代码贴了上来: -- 用协程和递归实现全排列 function permgen(a,n) n = n or #a if n "a\"problematic\"\\string"对于环形table和共享子table也要做特殊处理: a = {x = 1,y = 2;{3,4,5}} a[2] = a -- 环 a.z = a[1] -- 共享子table解决办法是使用一个额外的table来保存已经被记录过的table和它的名称,key是table,value是table的名称。 第13章 元表(metatable)与元方法(metamethod)13.1 算术类的元方法通过设置元方法可以让表具有加减乘除等算术操作。以加法 __add 为例: local mt = {} Set = {} function Set.new(l) local set = {} setmetatable(set,mt) for _,v in ipairs(l) do set[v] = true end return set end function Set.union(a,b) local res = Set.new() for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end mt.__add = Set.union s1 = Set.new(10,20,30) s2 = Set.new(30,1) s3 = s1 + s2如果两个表的元表不一样,lua会优先以第一个表的元表查找__add方法。如果没有再找第二个,都没有就报错。 元表支持的各种算术操作符如下: __add、__mul、__sub、__div、__unm(相反数)、__mod(取模)、__pow(乘幂)。 13.2 关系类的元方法除了算术运算符,还有关系运算符,元方法为:__eq(等于)、__lt(小于)、__le(小于等于)。 13.3 库定义的元方法这一节介绍了 __tostring 和 __metatable 的用法。 当调用print的时候会调用tostring函数。下面两个print是等价的: a = 1; print(a) print(tostring(a))而如果tostring里面是一个表,会调用表的 __tostring 元方法。 而 __metatable 用于保护元表。接上一节的例子 mt.__metatable = "error発生" s1 = Set.new() print(getmetatable(s1)) --> error発生 setmetatable(s1,{}}) -- stdin:1: cannot change protected metatable13.4 table访问的元方法1. __index元方法 当访问一个table不存在的元素时,会访问index的元方法。 - **如果index的元方法是一个函数,会以table和访问的key调用这个函数。 - 如果__index的元方法是一个表,会以访问的key去查找这个表里。** 2. __newindex 元方法 ndexindex同理,当对一个table不存在的元素赋值时,会访问newindex的元方法。 使用rawset和rawget可以绕过元表的index和newindex。 local mt = { __newindex = function(table, key, value) ...... end }3. 应用__index来设置table的默认值 如下的写法可以让所有需要默认值的table共用一个元表: local mt = {__index = function(t) return t.___ end} function setDefault(t,d) t.___ = d setmetatable(t,mt) end tab = {x = 10,y = 20} setDefault(tab,0) print(tab.x, tab.z) --> 10 04. 应用__index和__newindex来跟踪table的访问 5. 实现只读的table function readOnly(t) local proxy = {} local mt = { __index = t, __newindex = function(t,k,v) error("attempt to update a read-only table",2) end } setmetatable(proxy , mt) return proxy end第14章 环境14.1 具有动态名字的全局变量获取全局变量和赋值全局变量: value = _G[varname] _G[varname] = value如果变量名是类似"a.b.c.d"的情况应该这么写: function getfield(f) local v = _G for w in string.gmatch(f,"[%w_]+") do v = v[w] end return v endstring.gmatch 里面的参数用到了字符串模式,功能类似正则表达式,用来匹配字符串,有着复杂的规则。平时用不着,需要的时候再看:lua中的字符串操作模式。 14.2 全局变量声明由于lua将全局变量存放在一个普通的table里,所以可以通过元表来改变访问全局变量的行为 。 setmetatable(_G,{ __newindex = function(t,n,v) local w = debug.getinfo(2,"S).what if w ~="main" error("attempt to write to undeclared variable"..n,2) end rawset(t,n,v) end, __index = function(t,n) error("attempt to read undeclared variable"..n,2) end })这里面用到了debug.getinfo,__newindex里面几行代码的作用是判断是不是主线程调用,如果不是则报错,如果是则设置全局变量。 关于getinfo里的参数,第23章会详细说明。 14.3 非全局的环境lua里存放全局变量的表默认也是全局的,修改全局变量将会影响其他函数。但是可以使用setfenv来改变当前函数存放全局变量的表(也叫当前函数的环境)。 setfenv第一个参数为数字或函数,为数字时,表示第几层的函数,第二个函数为表。 a = 1 setfenv(1,{}) print(a) --> 会报错,print为nil上面这个例子改变了环境,所以之前能使用的全局变量或函数全部不能使用了。 注意每个闭包也有自己的环境: function factory() return function() return a end end a = 3 f1 = factory() f2 = factory() setfenv(f1, {a = 10}) print(f1()) --> 10 print(f2()) --> 3第15章 模块与包15.1 require函数require用于加载模块,返回由模块函数组成的table,如果已经加载过,会保存在package.load[name] 里。 local m = require "io" m.write("hello world\n")require用于搜索lua文件的路径放在package.path中。 15.2 编写模块的基本方法为了后期更改模块名的方便,不建议定义函数的时候使用真正的模块名。 local M = {} complex = M M.i = {r =0, i = 1} function M.new(r.i) return {r = r, i = i} end return complex最后一行的return可以用package.loaded[modname] 替代: local modname = ... local M = {} _G[modname] = M package.loaded[modname] = M15.3 使用环境使用环境可以改善写模块经常出现的两个问题: 忘记加local导致局部函数变成全局函数 调用模块内的公有函数时需要写前缀 比较麻烦local modname = ... local M = {} _G[modname] = M package.loaded[modname] = M setfenv(1, M)声明函数不再需要前缀时 就会变成http://modname.xxx: function add(c1,c2) return new(c1.r + c2.r, c1.i + c2.i) end上述的写法会产生无法调用原先的全局变量的缺点,解决方案有三: 使用元表setmetatable(M, {__index = _G}) 将 _G 保留起来,调用全局变量时增加 _G 前缀 提前声明需要调用的函数或者模块local sqrt = math.sqrt local io = io setfenv(1,M)15.4 module函数使用module(...) 可以代替下面这几行代码: local modname = ... local M = {} _G[modname] = M package.loaded[modname] = M setfenv(1, M)使用module(...,package.seeall) 相当于上面的代码加上: setmetatable(M,{_index == _G})15.5 子模块和包在定义模块名时可以加点,mod.sub 是mod的一个子模块。一个包是一个完整的模块树。执行require搜索文件时,会自动把模块名中的点转为分割符: ./?.lua -- require "a.b" ./a/b.lua注意: 加载子模块a.b时不会自动加载a 加载模块a时也不会自动加载子模块a.b第16章 面向对象编程在定义函数时,建议使用self: function Account.withdraw (self, v) self.balance = self.balance - v end有两个好处: 模块名发生变动时函数不受影响 可以调用有相同方法的其他对象调用时使用冒号语法隐藏self : Account:withdraw(100,100)16.1 类使用元表和__index的机制可以实现类实例: function Account:new(o) o = o or {} setmetatable(o,self) self.__index = self return o end16.2 继承继承的实现: SpecialAccount = Account:new() s = SpecialAccount:new(limit = 1000)编写新的方法: function SpecialAccount:getLimit() return self.limit or 0 end如果有一个对象需要特殊的行为,可以直接在对象中实现这个行为。 function s:getLimit() return balance * 0.10 end16.3 多重继承多重继承实现的关键在于不能将基类作为子类的元表: local function search(k,plist) for i = 1, #plist do local v = plist[i][k] if v then return v end end end function createClass(...) local c = {} local parents = {...} setmetatable(c,{__index = function(t,k) return search(k,parents) end}) function c:new(o) o = o or {} setmetatable(o,{__index = c}) return o end return c end多重继承由于需要查找父类,性能上不如单一继承,改进的方法之一是将继承的方法复制到子类中, 缺点是子类不能修改方法的定义。 16.4 私密性在lua里实现私密性的做法: 使用两个table来表示一个对象,一个table用于存储对象的状态,另一个用于操作 。 16.5 单一方法(single-method)做法当一个对象只有一个方法时,可以不用创建table,而是将这个单独的方法作为对象来表示返回 。 第17章 弱引用table在lua不会回收可访问table中作为key或者value的对象 。除非使用弱引用。 另外像数字和布尔这样的值类型是不会被回收的 。 __mode含有"v"表示表arr里的value是弱引用,含有"k"表示arr里的key是弱引用,含有"kv"则两者兼有。意思是当value或者key为nil时,GC之后表arr里不再拥有引用 t1,t2 = {},{} arr = {} arr[1] = t1 arr[2] = t2 t1 = nil setmetatable(arr,{__mode = "v"}) collectgarbage() for k,v in pairs(arr) do print(k,v) end -- 只有1个t2,t1已经被回收17.1 备忘录(memoize)函数备忘录函数的作用就是把经常调用的数据缓存起来,用空间换时间。这种函数就很适合使用弱引用。这个时候需要把value作为弱引用。 17.2 对象属性在lua里,table也是可以作为key的 。 当table作为key时,也需要使用弱引用,不然这个table也不会被回收,这种时候就需要 把key作为弱引用。 17.3 回顾table的默认值弱引用还有一个应用就是设置table的默认值。 这是将table作为key,默认值作为value存起来 : local defaults = {} setmetatable(defaults,{__mode = "k"}) local mt = {__index = function(t) return defaults[t] end} function setDefault(t,d) defaults[t] = d setmetatable(t,mt) end当然也可以将默认值作为key,table作为value : local metas = {} setmetatable(metas,{__mode = "v"}) function setDefault(t,d) local mt = metas[d] if mt == nil then mt = {__index = function() return d end} metas[d] = mt end setmetatable(t,mt) end第18章 数学库math库 由一组标准的数学函数构成,包括三角函数(sin、cos、tan、asin、acos等)、指数和对数函数(exp、log、log10)、取整函数(floor、ceil)、max和min、生成伪随机数的函数(random、randomseed),以及变量pi和huge。其中huge为lua可以表示的最大数字 。 第19章 table库19.1 插入和删除table.insert(list, [pos], value) 中间的pos如果不填就是插入到table末尾table.remove(list, [pos])19.2 排序table.sort(list, [comp]) 第二个参数是一个排序函数。这个函数需要两个参数,如果希望第一个参数排在第二个前面,返回true。注意sort功能只能是对table的value进行排序而不是key 。 19.3 连接table.concat(list) 接受一个字符串数组,进行连接并且返回结果。 第20章 字符串库20.1 基础字符串函数string.len(s) 返回s的长度strnig.rep(s,n) 返回字符串s重复n次的结果string.lower(s) 和 string.upper(s) 大小写转换 string.sub(s,i,j) 截取s的第i个到第j个字符strnig.char和strnig.byte 转换字符和内部数值表示strnig.format 格式化字符注意,字符串索引从1开始,也可以用负数 , -1表示倒数第一个。 20.2 模式匹配函数string.find 搜索字符串返回索引s = "hello world" i, j = string.find(s, "hello") print(i,j) -->1 5string.match 搜索字符串返回匹配的子字符串print(string.match("hello world","hello")) --> hellostring,gsub 替换字符串s = string.gsub("lua is cute", "cute", "great") print(s) --> lua is greatstring.gmatch(s, pattern) 返回一个迭代器函数,以模式对s做匹配s = "hello world from lua" for w in string.gmatch(s, "%a+") do print(w) end -- 打印每个单词20.3 模式模式可以用于匹配字符串,上节提到的函数都能使用模式,诸如下面这种格式: -- 检查字符串是否以数字开头 if string.find(s, "^[+-]?%d+$") then ...下面的字符都是模式里经常会出现的: %a %c %d %l %p %s %u %w %x %z () . % + - ? [] ^ $ 20.4 捕获捕获功能可根据一个模式从目标字符串中抽出匹配于该模式的内容。 pair = "name = Anna" key,value = string.match(pair,"(%a+)%s* = %s(%a+)") print(key,value) --> name Anna20.5 替换string.gsub 函数的第三个参数可以是一个函数或table。 - 是函数时,调用的参数就是捕获到的内容,函数的返回值作为替换的字符串 - 是table时,将捕获到的内容作为key,查看table,将对应的value作为替换的字符串 第21章 I/O库I/O 库提供了两套不同风格的文件处理接口。 第一种风格使用隐式的文件句柄; 它提供设置默认输入文件及默认输出文件的操作, 所有的输入输出操作都针对这些默认文件。 第二种风格使用显式的文件句柄。 当使用隐式文件句柄时, 所有的操作都由表 io 提供。 若使用显式文件句柄, io.open 会返回一个文件句柄,且所有的操作都由该文件句柄的方法来提供。 表io常用的函数如下: io.close io.read io.closet io.open第22章 操作系统库操作系统库定义在table os中,其中包含了文件操作数、获取当前日期和时间的函数。 第23章 调试库调试库由两类函数组成:自省函数(introoective function)和钩子(hook) 。 23.1 自省机制主要的自省函数是debug.getinfo 。第一个参数是调用层数或者函数,第二个参数用来设置要获得的信息,可以设置如下几种字符: 'n' : 填充 name 及 namewhat 域; 'S': 填充 source , short_src , linedefined , lastlinedefined ,以及 what 域; 'l': 填充 currentline 域; 't': 填充 istailcall 域; 'u': 填充 nups, nparams,及 isvararg 域; 'f': 把正在运行中指定层次处函数压栈; 'L': 将一张表压栈,这张表中的整数索引用于描述函数中哪些行是有效行。 (有效行指有实际代码的行,即你可以置入断点的行。 无效行包括空行和只有注释的行。)23.1.1 访问局部变量debug.getlocal 用来检查任意活动函数的局部变量。第一次参数是层数,第二个参数是索引。 debug.setlocal则用来改变局部变量的值。 function foo(a,b) local x do local c = a - b end local a = 1 while true do local name,value = debug.getlocal(1,a) if not name then break end print(name,value) a =a + 1 end end foo(10,20)打印结果: a 10 b 20 x nil a 4 第一个a,b 是函数的参数,没有c是因为执行到getlocal时c已经不在作用域了。 23.1.2 访问非局部的变量debug.getupvalue (f, up) 此函数返回函数 f 的第 up 个上值的名字和值debug.setupvalue (f, up, value) 这个函数将 value 设为函数 f 的第 up 个上值23.1.3 访问其他协同程序所有自省函数都能接受一个协同程序作为参数。 23.2 钩子debug.sethook ([thread,] hook, mask [, count]) 将一个函数作为钩子函数设入。 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义: 'c': 每当 Lua 调用一个函数时,调用钩子;'r': 每当 Lua 从一个函数内返回时,调用钩子;'l': 每当 Lua 进入新的一行时,调用钩子。 此外, 传入一个不为零的 count , 钩子将在每运行 count 条指令时调用。如果不传入参数, debug.sethook 关闭钩子。 23.3 性能剖析这一节展示了使用debug.sethook 来统计函数调用次数的案例。 第24章 CAPI概述CAPI是一组能使C与Lua交互的函数。包括读写Lua全局变量、调用Lua函数、运行一段Lua代码,以及注册C函数以供Lua代码调用。 Lua和C通过一个虚拟栈来进行通信。 24.1 第一个示例luaL_newstate 创建状态机luaL_openlibs 打开标准库lua_pcall 调用代码块luaL_loadbuffer 加载代码块lua_pop 弹出元素24.2 栈压入元素:lua_push* 检查栈空间:lua_checkstack检查元素:lua_is* 。注意lua_isnumber是检查值能否转为数字类型,lua_isstring同理。获取元素:lua_to*其他栈操作: lua_gettop、lua_settop、lua_pushvalue、lua_remove、lua_insert、lua_replacelua使用索引来引用栈中的元素,栈底为1,往上递增,栈顶为-1,往下递减。 24.3 CAPI中的错误处理lua使用C的setjmp机制来处理异常。 24.3.1 应用程序代码中的错误处理使用panic 函数,只要不返回就可以不退出程序让代码在“保护模式”下运行24.4.2 库代码中的错误处理lua_error:以栈顶的值作为错误对象,抛出一个 Lua 错误 第25章 扩展应用程序25.1 基础lua_getglobal 获取全局变量,入栈25.2 table操作lua_gettable 获得索引处的tablelua_getfield 获得索引处的table的某个key对应的值lua_settable lua_setfield 25.3 调用Lua函数lua_getglobal 压入函数lua_push* 压入参数lua_pcall 完成调用lua_is* 检查结果第26章 从Lua调用C26.1 C函数所有注册到lua中的函数都具有相同的原型,这个时候使用的是局部栈,当lua调用一个C函数时,第一个参数总是这个局部栈的索引1: typedef int (*lua_CFunction) (lua_State *L);返回值表示它需要向Lua返回几个值。详细参见:lua_CFunction void lua_pushcfunction (lua_State L, lua_CFunction f) 将一个 C 函数压栈 26.2 C模块void lua_register (lua_State L, const char *name, lua_CFunction f) 把 C 函数 f 设到全局变量 name 中 第27章 编写C函数的技术27.1 数组操作int lua_rawgeti (lua_State *L, int index, int key); 把 t[key] 的值压栈, 这里的 t 是指给定索引处的表,不触发元方法void lua_rawseti (lua_State *L, int index, int key) ; 等价于 t[key] = v , 这里的 t 是指给定索引处的表, 而 v 是栈顶的值。 这个函数会将值弹出栈。 赋值是直接的;即不会触发元方法。27.2 字符串操作const char lua_pushlstring (lua_State L, const char *s, size_t len); 把指针 s 指向的长度为 len 的字符串压栈const char lua_pushfstring (lua_State L, const char *fmt, ...); 把一个格式化过的字符串压栈, 然后返回这个字符串的指针。void lua_concat (lua_State *L, int n); 连接栈顶的 n 个值, 然后将这些值出栈,并把结果放在栈顶27,3 在C函数中保存状态对于lua函数来说,有3种地方可以保存非局部的数据:全局变量、函数环境和非局部变量(closure中)。 CAPI中提供了3种地方来保存这类数据:注册表、环境和upvalue。 27.3.1 注册表(registry)注册表位于伪索引 上,这个索引由LUA_REGISTRYINDEX定义。 int luaL_ref (lua_State *L, int t)L传入luaState的指针,t传入*LUA_REGISTRYINDEX。这个函数的作用是弹出栈顶的值,并且用一个新分配的整数key把这个值注册到注册表里,然后返回这个整数key。这个key被称为"引用"。int lua_rawgeti (lua_State L, int index, lua_Integer n)L传入luaState的指针,index传入*LUA_REGISTRYINDEX,n传入上一个函数返回的整数key。作用是把注册表里的key对应的值压栈。 void luaL_unref (lua_State L, int t, int ref)L传入luaState的指针,t传入*LUA_REGISTRYINDEX,ref传入luaL_ref返回的整数key。27.3.2 C函数的环境设置环境的代码如下: lua_newtable(L); lua_replace(L, LUA_ENVIRONINDEX); luaL_register(L, , |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |