《Lua程序设计》读书笔记

还是在工作之后才听说的Lua脚本语言--游戏制作领域的脚本语言。不过算来也不是第一次接触,原先喜欢的游戏魔兽世界就是以lua作为脚本语言的。

lua在的读法是"鲁啊"是月亮的意思。

·要点:

基础语法:

--常规说明:

注释方法:单行注释方法是以两个连字符(--)开始一个行注释;

块注释方法是以"—[["开始以"]]"(推荐是"—]]")结尾,

可在[[之间加任意=,而对应的]]之间也要有相同数量的=;

程序块:使用关键字do (换行) <body> (换行) end的形式;

与Python一样不需要使用";"表示语句的结束;

特殊类型nil表示无效值--为初始化的全局变量就是nil,也可通过赋值nil来删除

变量--这是垃圾回收器会自动销毁变量;

类型和值:

lua中的类型有:nil,boolean,number,string,userdata,function,thread,

table(核心,同时也是唯一的数据结构);

nil:用于表示"无效值"的概念;

boolean:可选值true/false,其中为假值的情况只有false和nil;

number:表示实数,lua中没有整数类型;

userdata:主要用于跟C程序交换数据时使用;

function:函数--作为"第一类值"来看待;

thread:协同程序中使用;

string:不可变值,表示"一个字符序列";

完全采用8位编码--可将任意二进制数据存储到一个字符串中;

可采用单引号和双引号两种方式定义,支持转移字符;

多行字符串定义可采用[=[和]=]形式,其中=数量任意;

字符串连接操作符是".."--如果后接数字需要添加空格;

系统提供数字和字符串的自动转换;

操作符"#"可以获取字符串的长度;

table:有点像数据表,索引可以是除nil外的任意值;

可以动态增减大小,是lua中主要且仅有的数据结构机制;

通过table可以实现数组、符号表、集合、记录、队列等数据结构;

模块、包和全局空间都是table结构,同时面向对象也通过table实现;

在Lua中table是"对象"--即使用的是table的应用;

使用构造表达式"{}"来创建table;

table永远是"匿名的",变量只是持有的对table的应用;

访问table元素有两种方法:a["key"]和a.key是等价的;

需要注意的是table的索引默认的话是从1开始的--这和其他语言不同;

通过操作符#可获取table的最大数字索引值--需小心索引空隙;

表达式:

--算术操作符:有-(负号),+,-,*,/,^(指数),%

其中取模%规则是:a%b=a-floor(a/b)*b;

对于整数是通常意义上的取模操作,结果符号与b相同;

对于实数,x%1得到x的小数部分;

对于实数x,x-x%0.01得到x精度到小数点后两位的结果;

--关系操作符:有<,<=,>,>=,==和~=六个;

nil自与自身相等;

对table,userdata和fucntion比较引用--引用相同对象才相等;

--逻辑操作符:有and,or和not三个;

and和or都使用短路求值;

惯用法:x=x or v用于对未初始化x设置默认值v;

惯用法:(a and b) or c类似于C++中的a?b:c语法;

--字符串连接操作符:操作符".."(两个点)

即使两个操作符都是数字也会先将数字转变为字符串在连接;

字符串是不可变量,所以连接操作后得到的是新字符串;

--操作符优先级:^>(not#-)>(*/%)>(+-)>..>(<><=>=~===)>and>or

--table构造式:列表风格:a={"Sunday","Monday","Tuesday"}

记录风格:a={x=10,y=-2}

流程控制语句:

--赋值:修改变量或table元素值,更改变量的引用;

Lua支持多重赋值--可交换变量和函数返回多个值;

--局部变量与块:通过local语句可以创建局部变量;

--分支控制if:if语句格式:if cond then

body

elseif cond then

body

else

body

end

--whil循环:while格式:while cond do

body

end

--repeat循环:repeat格式:repeat

body

until cond

repeat应该是替换C++中的do…while循环;

--数字型for循环:for var=exp1,exp2,exp3 do

body

end

--泛型for循环:通过迭代器函数来遍历所有值;

for k[,v] in pairs(t) do

body

end

--break和return:

break语句用于结束一个循环;

return语句主要用于在函数中返回结果和结束函数执行;

需要注意的一点是break和return只能是一个块的最后一条语句;

函数:函数是一种对语句和表达式进行抽象的主要机制;

在lua中如果参数是字面字符串或table构造式函数调用可以省略括号;

对象调用方法有两种方式:obj.func(x,y)和obj:func(x,y);

函数定义方法:function func_name(args)

body

end

lua会自动匹配参数和实参--多余舍去不足用nil初始化;

默认实参:使用参数自动匹配和n=n or 1语句来设置默认参数;

--多返回值:lua会调整返回值的数量以适应不同的调用情况:

只有作为表达式最后一个元素时才获取所有参数,否则只得到第一个返回值;

用到多返回值的情况:多重赋值、函数实参、table构造式、return语句;

在return语句中,使用括号将强制值返回第一个返回值;

--变长参数:语法格式:fucntion fucn_name(arg1,…)

body

end

操作符"..."表示该函数可以接受不同的实参--即变长参数;

表达式"..."表示一个由所有变长参数构成的数组;

使用:"a,b=…"的形式或"for i,v in ipairs{…}"或"return …"形式;

可通过select()函数来访问可变参数"..."中指定位置的参数;

--具名参数:具名参数是指调用是采用"func_name(a=b)"的形式来指定实参;

可通过将参数设定为table的方法来间接实现具名参数;

--匿名函数:定义形式:foo=function (x) return 2*x end的形式;

--闭合函数:Lua中可在函数中定义函数,且内部函数可访问外部函数的局部变量;

外部函数的局部变量在内部函数中叫非局部变量--不会在外部函数推出后失效;

closure(闭合函数)是{函数+该函数所需访问的所有非局部变量};

同一函数的不同closure的非局部变量是不相干的;

利用闭合函数(closure)可以进行函数式编程;

--非全局的函数:指将函数存储在table字段或局部变量中的函数;

局部函数定义:local f=function (args) body end

local function foo(args) body end

table字段函数:tab.foo=function (args) body end

tab={foo=function (args) body end}

function tab.foo(args) body end

--递归函数:在定义递归函数时需要先将函数进行local func初始化;

--尾调用:只有"return func_name(args)"形式作为结尾才是尾调用;

尾调用市不会保留上一个函数状态--类似goto不会保留函数调用链;

一个典型应用是编写"状态机";

迭代器与泛型for:

迭代器是一种可以遍历集合中所有元素的机制--主要为for编写;

迭代器是调用一次返回集合中下一个元素的函数;

泛型for会做所有的薄记工作,而迭代器只需要正确返回集合中下一个元素;

迭代器包含两部分:工厂函数如ipairs()函数和工作器函数;

为了正确返回集合中的下一个元素,迭代器需要保存状态:

使用closure机制;

使用泛型for的恒定状态和控制变量来记录;

扩展恒定状态为table来传递复杂状态;

编译、执行和错误:

Lua允许先将源代码预编译为一种中间形式,相关函数:

dofile():编译并执行lua文件;

loadfile():编译lua文件但不执行,以函数形式返回而函数调用就是执行;

loadstring():与loadfile()功能一样,但参数是string;

底层函数package.loadlib()可以加载动态库;

--错误处理:

作为脚本语言,lua把异常处理交给了宿主语言来处理而只提供错误信息;

错误处理方式:

返回nil和错误信息;

通过error()函数抛出异常,相关build-in函数是assert()函数;

可以用pcall()函数包装需要执行的代码;

协同程序(coroutine):

协同程序是一条执行序列,拥有自己独立的栈、局部变量和指令指针,

同时与其他协同程序共享全局变量和其他大部分共享资源;

与线程的区别:

协同程序像同步后的线程,每次只有一个协同程序在执行;

协同程序只能自己要求挂起(可通过其他方式改变);

所有的协同程序相关函数放在"coroutine"的table(库)中;

协同程序的数据类型为thread类型;

协同程序的4种状态:

挂起(suspended):创建和调用yield()后进入该状态;

运行(running):通过resume()启动协同程序后进入该状态;

死亡(dead):执行完协同程序后进入此状态;

正常(normal):调用resume的协同程序处于此状态;

相关函数:

coroutine.create(func):创建协同程序;

coroutine.resume():启动协同程序;

coroutine.yield():挂起协同程序;

coroutine.statue():检查协同程序的状态;

一个比较好的例子是生产者/消费者模型;

数据结构:

Lua中的数据结构都是通过table来实现的;

--数组:使用数字做索引的table;

需要注意的是Lua中的惯例是索引从1开始的;

--矩阵和多维数组:

实现多维数组的两种方法:一种是使用table中嵌套table的方法;

还一种是将二位数组变更为一维后存储在table中;

因为table本身就是稀疏的,所以用table实现的稀疏表不存在内存开销问题;

--链表:只需要将table的一个字段持有下一个table的引用就可以了;

--队列和双向队列:通过table库的插入/删除函数可模拟队列行为;

--集合与无序组:可将table索引定义为元素,将值定义为真假值来实现集合概念;

--字符串缓冲:因为字符串是不可变值,像连接操作会产生新字符串开销,

可使用table来缓存字符,最后用table.concat()来连接成字符串;

--图:也可通过table来表示图的概念;

对应不同的图表示有不同的算法匹配;

数据文件与持久性:

--数据文件:

数据文件指按固定格式(如html/xml等)存储数据的文件;

lua拥有自己的数据存储方法:Enty{}或Enty{key=value}形式,

可在程序中定义Enty函数来处理数据

--因为调用table可省略括号所以数据就变成了函数调用;

--持久性:可将lua代码保存为字符串形式--需要注意转移字符;

元表与元方法:

元表:定义了一个table行为属性的另一个table;

元方法:元表中定义的行为函数;

table和userdata像类一样可以定义自己的独立元表,

而其他类型则共享所属类型的元表行为,且需要通过C代码设置;

相关函数:getmetatable()和setmetatable()两个函数;

--算术类的元方法:__add,__sub,__mul,__div,__unm(取反),__mod,

__pow,__cancat;

查找元方法顺序:先在第一个值元表中查找,然后是第二个值,都没有会报错;

--关系类元方法:__eq(==),__lt(<),__le(<=)三种基础比较;

其他的关系比较操作都是通过上述三种基础比较操作来实现;

不支持混合比较和拥有不同元方法的比较;

--库定义的元方法:程序库在元表中定义自己的字段;

常用字段:metatable.__tostring():printh函数调用时会调用;

metatable.__metatable():保护metatable不会被得到/修改;

--table访问的元方法:提供改变table行为的方法,相关方法:

__index:查找元素是调用,在访问table中字段时,先在tablez中查找,

然后调用__index()函数返回结果,都不存在时返回nil;

__index既可以赋值为函数也可以赋值为table;

可通过函数rawget来禁用__index的方法;

__newindex元方法:用于赋值给不存在的字段时调用;

当__newindex赋值为table的话,调用__newindex会

对绑定的table赋值而不是更改原table;

使用__index和__newindex可实现:只读table/具有默认值table/继承关系;

环境:具有一定生命期的保存相应数据的table结构;

lua将所全局变量保存在环境table中--环境table自身保存在全局变量_G中;

为防止对全局变量的误操作可以给全局环境table设置元表;

可通过函数setfenv()来设置函数的环境,并可以通过在非全局环境中包含

全局环境来使用全局变量;

模块与包:

模块就是程序库,而包则是一个完整的模块库--整合库/Lua的发行单位;

模块和包的搜索路径:当前目录=》环境配置目录,会查找lua文件和C文件;

包采用文件夹结构,需要包含一个init.lua的文件;

--加载:

加载函数是require()--当参数是字符串时一般省略括号;

函数require()会加载模块中的变量和函数等数据结构到全局变量中;

--编写module:

在文件开始创建table,中间定义字段函数,最后返回table;

为了增加模块特性可扩展模块结构;

新增函数module会自动应用扩展来增强模块特性;

面向对象编程:

在Lua中更能体现面向对象是一种思想--通过table实现面向对象编程;

用惯了C++方式的面向对象方法在使用Lua的面向对象很不习惯;

--类:

通过table的元表和__index元方法可实现类的概念;

通过在元表中定义new方法可实现类的构造函数;

语法obj.func(obj,args)等价于obj:func(args)--可隐藏self/this;

--继承:

单继承的实现通过元表和__index表来实现;

--多重继承:

通过__index函数来实现多继承--但以为函数调用存在性能开销;

--私密性即封装:

将方法和数据分离放入不同的table中--不常用的技巧;

弱引用table:

弱引用—一种会被垃圾收集器忽视的对象引用

弱引用table:具有弱引用条目的table

Lua只会回收弱引用table中的对象—而不是值(字符串也是值)

实现方法是通过元表的__mode字段设置为带k/v的字符串

--备忘录函数

根据空间换时间的思想可缓存计算结果来减少频繁操作的耗时

而缓存的存储则花费空间—即内存

弱引用可以在结果没有使用时由垃圾回收器自动回收内存

--对象属性

将对象作为key来关联属性时,如引用解决了无法删除对象key

弱引用table的应用--回顾table的默认值

标准库:

--I/O库:

--数学库:由一组标准的函数构成:

三角函数(sin,cos,tan,asin,acos等),指数对数函数(exp,log,log10等),

取整函数(floor,ceil,max和min),变量pi和huge(最大数字),

生成伪随机数函数(math.random,math.randomseed)

--table库:由一些辅助函数构成,这些函数将table作为数组来操作:

插入和删除(table.insert,table.remove),排序(table.sort),

连接(table.concat)

--字符串库:

原始字符串操作:创建字符串,连接字符串和获取字符串长度

在5.1中也将string库的函数导出为字符串类型的方法(元表实现)

表示位置时需要注意的是:起始位置为1且负数表示从结尾计数

因为字符串是不可变值,所以返回字符串都是新字符串

基础字符串函数:string.len(s),string.rep(s,n),string.lower(s),

string.upper(s),string.sub,string.charstring.byte,

t={s:byte(1,-1)},string.char(unpack(t)),string.format;

--模式匹配函数

没有采用POSIX和perl的正则表达式方式

相关函数:

string.find –找到完全匹配时返回起始索引和结尾索引

--也可以含开始搜索的开始位置参数

string.match –与string.find类似,但返回的是子串

string.gsub –用指定参数替换所有匹配

--也有可选参数来指定替换次数

string.gmatch –返回的是函数,用在泛型for中可作为迭代器使用

模式

跟正则表达式中的模式是一种概念,但是lua自己的规则

定义好的字符分类

.(所有字符)%a(字母)%d(数字)%w(字母和数字字符)

%l(小写字母)%u(大写字母)

%c(控制字符)%p(标点符号)%s(空白字符)

%x(十六进制数字)%z(内部表示为0的字符)

--大写形式表示的是补集

魔法字符—需要用%来转义

--().%+*-?[]^$

--因为这些字符都有特殊含义需要转义符号%来转义

自定义字符分类

--定义方式是用[]将规则放入其中间位置,如二进制[01]

--可以通过符号”-”表示区间,如八进制[1-7]

--符号”^”表示取反操作—即得到补集

重复性修饰符

+ 重复1次或多次

* 重复0次或多次

- 也是重复0次或多次

? 出现0或1次,表示可选概念

--其中-和*的区别可以通过例子来理解:

对于C++中/*和*/,符号*会尽可能多的匹配—即匹配最后*/

而符号-则尽可能少的扩展来找到第一个*/

特殊符号

如果模式以^开头则只会匹配目标字符串的开头部分

如果模式以$结尾则只会匹配目标字符串的结尾部分

可使用%b<x><y>来匹配成对的字符

捕获

捕获的概念是有选择的从目标字符串中提取匹配内容

表示为将需要捕获的模式放入在()中

可以使用”%数字”的形式来引用其他捕获

替换

主要是扩展string.gsub函数,即替换的目标可以是函数或table

当第三个函数或table字段返回nil时不做替换

技巧

--没看

--操作系统库:

--调试库:

·小结: