【读书笔记】《Lua程序设计》(第1部分)

2018年7月18日 4504点热度 0人点赞 0条评论

第一章    开始

lua是一种解释执行语言,一般后缀名为.lua

  • 1.1  程序块
    • 一个程序块是一连串的语句和命令,可以是任意大小的,lua程序块件不需要符号分割,不过也支持使用分号分割
    • lua通常作为一种数据描述语言来使用,即使几兆的程序,lua解释器处理也没有问题
    • 退出lua命令行可输入ctrl+z(Linux下为ctrl+d),或调用lua函数 “os.exit()
    • lua 参数 -i 可以指定在打开交互命令前,先打开哪些文件,dofile(parameter)可以加载lua程序,例:lua -i  prog
  • 1.2  词法规范
    • lua变量由数字、字母、下划线组成,首位不能为数字,例:iceb123
    • 下划线后跟一个或多个大写字母用做特殊用途,只有一个下划线为哑变量?
    • 通过设置区域设置(Locale)来设置语言,例:
      os.setlocale("chs")
    • lua区分大小写
    • lua行注释以 “--” 开始,块注释以“--[[”开始,“]]”结束
  • 1.3  全局变量
    • lua中全局变量不需要声明,也没必要删除,默认全是全局变量
    • 通过将变量赋值为nil强制删除该变量
  • 1.4  解释器程序
    • lua脚本可以在第一行加 “#!/usr/bin/lua”的方式,直接调用脚本
    • lua -e 直接在命令行输入代码,例:lua -e "print('hello')"
    • lua -l 用来加载库文件,例:lua -i -l a 会先加载库a,再进入交互
    • lua参数索引以文件名索引为0,之后参数为正,之前为负,依次递增或缩减

第二章    类型与值

lua是动态类型语言,值本身即“带有”自身类型信息 lua有8中基本类型:nil(空)、boolean(布尔)、number(数字)、string(字符串)、userdata(自定义类型)、function(函数)、thread(线程)、table(表) 可使用type()查看对象的类型,type()返回值永远是字符串 lua中函数为第一类值

  • 2.1  nil (空)
    • lua将nil用于表示无效值,用于区别其他值
  • 2.2  boolean (布尔)
    • boolean类型有两个可选值,falsetrue
    • lua将false和nil视为,其余所有值都为
  • 2.3  number (数字)
    • number表示实数,lua没有整数
    • lua进行算术运算不用担心精度问题,除非数非常大,比如大于10的14次方
    • 可通过重新编译lua的方式改变其数字类型
  • 2.4  string (字符串)
    • lua字符串是不可变值,由自动内存管理机制所管理
    • lua可以高效地处理长字符串
    • 字面字符串由匹配的单引号双引号界定
    • 可以通过转义字符指定字符串,转义序列为反斜杠跟一至三个数字序列,例:“\97”和“a”一样,“\097”和“\97”一样
    • 可以用一对匹配的括号界定一个字符串,且lua不会转义其中转义序列,例:str = [[\h]] 中str值为“\\h”,不会转义
    • 可以通过在左右双括号间加上相同数量的等号,达到之前相同的效果,这同样适用于块注释
    • lua提供了数字与字符自动转换
    • ".."是字符串连接操作符,在数字后使用时要加空格防止被认为是小数点
    • 不要依赖强制转换
    • tonumber()可将字符转换为数字,无法转换则为nil
    • tostring()可将数字转换为字符
    • 使用”#“前置操作符可获取字符串长度,例:#"hello" 值为5
  • 2.5  table (表)
    • table可通过nil外的类型充当索引
    • table无法声明,通过"构造表达式"完成
    • 持有table的变量与table本身没有固定关联性
    • table可以有不同类型的索引
    • 可以将形如:a["name"]简写为a.name,注意a[name]不等于a["name"]
    • 长度操作符"#"可用于返回一个数组的长度或线性表的最后一个索引值
    • nil通常用来界定数组结尾,当一个数组有nil时,有可能被认为是数组结尾,因此要避免空隙,可以使用table.maxn来获取最大索引,此方法在5.1中新增,5.2中删除
  • 2.6  function (函数)
    • lua函数为第一类值,可以存储在变量中,可通过函数传递,也可做为返回值
    • lua对函数式编程提供了良好的支持,第六章
    • lua可调用C语言,其所有标准库都是C语言写的
    • 第五章讨论lua函数,二十六章讨论C语言编写lua函数
  • 2.7  USERDATA (自定义类型) 和 thread (线程)
    • userdata用于表示一种由应用程序或C语言创建的新类型
    • 第九章解释thread类型,并讲到"协程"

第三章    表达式

lua的表达式不仅可以包含数字、字符串、变量、操作符、函数调用,还包含函数定义table构造式

  • 3.1  算术操作符
    • lua支持常规的算术操作符,加、减、乘、除、指数、取模、取负
    • lua的取模操作结果的符号第二个参数相同
    • 取模操作可以用来求实数小数部分,精确两位等等,例:x-x%0.01
  • 3.2  关系操作符
    • lua提供了关系操作符"< > <= >= == ~="
    • lua安照字符次序比较字符串的大小,例:"15" > "2"
  • 3.3  逻辑操作符
    • lua逻辑操作符有"and or not"
    • and or 返回值为操作数, not返回true或false,例:5 and 1 返回1,5 or 1返回5
    • and or 为短路求值
  • 3.4  字符串连接
    • 字符串连接操作符为".."两个点,例:"he".."llo"为"hello"
    • 对lua字符串进行连接操作不会改变原有值
  • 3.5  优先级
  • 3.6  table构造式 (table constructor)
    • 构造式用于创建和初始化table
    • 例子:days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} ,其中days[4]  为Wednesday
    • 例子:a = {x = 10, y = 20} ,等价于a = {}; a.x = 10; a.y=20
    • lua可以在构造table元素时,最后加上一个逗号
    • lua在构造table元素时,可以用分号替代逗号

第四章     语句

lua语句包括赋值、控制结构、过程调用,此外还支持多重赋值局部变量

4.1  赋值
  • lua支持多重赋值,多个变量以及多个值分别需要用逗号隔开,例:x,y = y,x
  • lua的赋值操作先把右边的结果计算出来,然后再赋值给右边
  • 若值的个数小于变量的个数,多余的变量值为nil,反之多余的值会被弃掉,例:x,y  =  1 此时y等于nil
4.2  局部变量与块
  • 通过local创建局部变量,例:local i = 1
  • 一个块可以是一个控制结构执行体,也可能是一个函数执行体或者是一个程序块
  • 交互模式中每一行都是一个程序块
  • 当需要显式界定一个块时,可以将块放入“do-end”中
  • 尽可能使用局部变量
  • 局部变量作用域从声明到本块结束
  • 通常使用相同的全局变量初始化局部变量,local foo = foo
4.3  控制结构

lua提供了用于条件执行的if,用于迭代的while,repeat,for,这些操作都有明确的结尾符,if,while,for为end,repeat为until

4.3.1 if then else
  • else部分是可选
  • 嵌套if可使用elseif简化
  • lua不支持switch
4.3.2 while
  • while语法和其他语言一样
4.3.3 repeat
  • repeat语句至少执行一次
  • 局部声明在循环体的作用域包含了条件测试部分
4.4.4 数字型for(numeric for)
  • for包括数字型for泛型for
  • 数字型for语法为:
    • for var = exp1,exp2,exp3 do
      • <执行体>
    • end
  • 其中var从exp1变化到exp2,每次变化步长为exp3,默认步长为1
  • 不想给循环设置上限可以使用math.huge作为第二个参数
  • for的三个表达式是在循环开始前一次性求值的,其第一个表达式变量默认为局部变量,第二个表达式不会每次求值的
  • 不要在循环过程中修改控制变量的值
4.3.5 泛型for(generic for)
  • 泛型for通过迭代器来遍历所有值,示例如下
    • for  i,v  in  ipair(a) do
      • print(v)
    • end
  • 泛型for的迭代器很强大,将在第7章具体讨论
  • 同数字for一样,泛型for的循环变量也是局部变量不应该在循环体内修改循环变量的值
4.4  break与return
  • break与return都用于跳出当前块
  • break只会跳出包含它的内部循环
  • return从函数返回结果,或结束函数的执行
  • 使用do-return-end的方式提前结束函数,如
    • function foo()
      • return语法错误
      • do return end
      • <其他语句>
    • end

第五章    函数

一个函数若只有一个参数,且此参数是一个字面字符串或table构造式,则圆括号可有可无,例print "Hello, World" lua提供了冒号操作符,隐含的将调用者作为第一个参数,例,o.foo(o,x)等于o:foo(x) lua函数默认参数机制,形参多余实参时,多余参数被丢弃,形参少于实参时,少的参数为nil

5.1  多重返回值
  • lua可以返回多个参数,在return关键字后列出即可
  • 若函数调用是最后一个表达式,lua尽可能保留尽可能多的返回值,若不是的话,只能产生一个值
  • 函数调用作为另一个函数调用的最后一个参数时,会将其所有返回值作为实参传递给第二个参数,否则只返回一个参数
  • table构造式作为形参可以完整地接受所有实参
  • 使用圆括号将多个内容括起来,只返回一个
  • unpack()可以返回数组中的所有
5.2  变长参数
  • 参数"..."表示参数可接受不同数量实参,被称为变长参数
  • 当需要访问变长参数时,仍使用"..."来作为表达式,例:local tb1 = {...}
  • 调用select时,必须传入一个固定实参selector(选择开关)和一系列变长参数。如果selector为数字n,那么select返回它的第n个可变实参,否则只能为字符串"#"
  • 关于lua中table的遍历问题,构造时不指出索引则按照构造顺序遍历,若指出索引则按照索引的哈希值顺序遍历,没有特殊要求,永远不要在构造时table中加入nil
5.3 具名参数
  • lua参数传递是按位置来的,目前并不支持指定名称来传递参数
  • lua可以通过传递一个列表,来实现参数名称与参数的手动对应

第六章    深入函数

6.1 closure(闭合函数)
  • 将一个函数写在另一个函数内,这个内部函数可以访问外部函数中的局部变量,这称词法域
  • 一个closure就是函数加上改函数所需的“非局部变量”
  • 在Lua中,函数为第一类值,当函数的返回值为函数时,每次调用外部的函数,都会返回一个新的函数,并且创建一个新的“非局部变量”
6.2 非全局的函数(non-global function)
  • 函数为第一类值,因此可以将函数存储到一个局部变量中,即得到一个“局部函数”
6.3 正确的尾调用(proper tail call)
  • Lua支持尾调用的消除,在使用尾递归时不用考虑堆栈溢出的情况

第七章 迭代器与泛型for

7.1 迭代器与closure
  • 迭代器指可以遍历集合中元素的机制,lua中通常表示为函数,每次调用迭代器函数,即返回集合中的下一个元素。
  • closure机制可以保存一些状态,因此非常适合做迭代器
7.2 泛型for的语义
  • 泛型for在循环过程中内部保存了一个迭代器函数,一个恒定状态和一个控制变量
  • 通常使用pairs来遍历一个列表
7.3 无状态的迭代器
  • 使用closure函数作为迭代器会产生一些开销,没有必要时,可采用无状态的迭代器,如pairs,ipairs
7.4 具有复杂状态的迭代器
  • 通常基于closure实现的迭代器比使用table迭代器更高效,创建closure比创建table廉价,访问“非局部的变量”也比访问table字段更快,使用协程创建迭代器将在之后讨论
7.5 真正的迭代器

第八章 编译、执行与错误

Lua是一种解释型语言,虽然Lua允许先将源代码编译为一种中间形式,区分解释型语言的关键在于是否能轻易地执行动态生成的代码。

8.1 编译
  • dofile用于允许Lua代码块,loadfile用于编译代码块,并将结果返回,loadfile比较灵活,
  • loadstring在Lua5.3中已经删除,可以直接使用load从字符串中读取代码
  • load总是在全局环境中编译它的字符串,load典型应用是处理外部代码,如用户输入的代码
8.2 C代码
  • C代码使用前需要链接,通常使用动态链接技术,动态链接不是ANSI C标准,Lua通过为多平台设计不同的动态链接机制
  • 使用package.loadlib(path,func)
8.3 错误(error)
  • error函数传入一个错误消息参数
  • assert函数检查第一个参数是否为true,若true则返回,false或nil则引起错误
  • 处理异常应当视情况而定,通常是停止计算,给出错误消息
  • repeat <....block..> untile <true>
8.4 错误处理与异常
  • 通常Lua中不做任何处理,应当在外部调用中处理异常
  • pcall包装需要执行的代码,输入一个可调用参数,返回错误类型和内容
8.5 错误消息与追溯(traceback)
  • Lua错误报告包含了代码所在行,还可附加层级
  • xpcall除了接受调用函数,还接受错误处理函数
  • debug.debug提供Lua提示符让用户检查错误原因,debug.traceback根据调用栈构建错误消息

第九章 协同程序(coroutine)

协同程序和线程相同之处在于,他们都有自己独立的栈、局部变量和指令指针,同时和其他协程共享全局变量和大部分东西。区别在于,多线程可以同时运行,而多个协程在同时只能有一个协程在运行

9.1 协同程序基础
  • lua协同程序函数放在“coroutine”的table中,
  • create表示创建一个协程,返回thread类型值,表示新的协程
  • 协程有4种状态,挂起、运行、死亡和正常,协程创建完默认处于挂起状态,可使用函数status来检查协程的状态
  • resume用于启动或再次启动一个协程,将其状态由挂起改为运行
  • yield函数可以让一个运行中的协程挂起
  • resume是在保护模式中运行,若协程执行中发生错误,会返回false和错误消息
  • 协程调用另一个协程时,调用者处于normal状态
  • resume传入的多余的参数为传入协程的函数,yield返回的函数为resume的额外返回值
9.2 管道(pipe)与过滤器(filter)
  • 使用协程模拟经典的生产者-消费者问题,该模型为消费者驱动
function send(x)
    coroutine.yield(x)
end

function receive()
    local status, value = coroutine.resume(producer)
    return value
end

producer = coroutine.create(
    function()
    local counter = 0
    while true do
        print "the producer produces products"
        send(counter)
        consumer = counter + 1
    end
    end
)

function consumer()
    while true do 
        print "the consumer consumes products"
        print(receive())
    end
end
 
consumer()
  • 在receive函数中处理数据,可将该模型修改为一个过滤器
9.3 以协同程序实现迭代器
  • 根据协程的特性,当调用yield时,协程挂起,再次调用时接着运行
  • Lua提供了一个wrap函数,类似create,用来创建新的协程,但它返回一个函数,每次调用这个函数时,便会唤醒协程,且它不是在安全模式下,因此不会返回错误,只会引发错误
9.4 非抢先式的(non-preemptive)多线程

第十章 完整的示例

10.1 数据描述
10.2马尔科夫链(markov chain)算法

icebmji

这个人很懒,什么都没留下