《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时不做替换

技巧

  --没看

--操作系统库:

--调试库:

·小结:

原文地址:https://www.cnblogs.com/davidyang2415/p/2621504.html