摘记 – 《Lua程序设计》

3 数值
  1. 数学库提供了三个取整函数:floor,ceil和modf。其中,floor向负无穷取整,ceil向正无穷取整,modf向零取整。
5 表
  1. 可以认为,表是一种动态分配的对象,程序只能操作指向表的引用。除此之外,Lua语言不会进行隐藏的拷贝或创建新的表。(注:此处所谓的隐藏的拷贝是指深拷贝,即拷贝的对象的引用而非整个对象本身。
  2. Lua5.3引入了一个更通用的函数table.move(a, f, e, t),调用该函数可以将表a中f到e的元素移动到位置t上。函数table.move还支持使用一个表作为可选的参数,当有该可选参数的时候,该函数将第一个表中的元素移动到第二个表中。
--在表a的开头插入一个元素
table.move(a, 1, #a, 2)
a[1] = newElement
--删除表a第一个元素
table.move(a, 2, #a, 1)
a[#a] = nil
--返回表a的一个克隆
table.move(a, 1, #a, 1, {})
--将表a所有元素复制到表b末尾
table.move(a, 1, #a, #b+1, b)
6 函数
  1. 默认参数
function func(n)
    n = n or 1
end
  1. 将函数用一对圆括号括起来可以强制其只返回一个结果
print((func()))
  1. 可变长参数函数

    function add(...)
        for _,v in ipairs{...} do
           --do something 
        end
    end
    
    1. select函数
    --函数select具有一个固定的参数selector,以及数量可变的参数
    --如果selector是数值n,那么函数select返回第n个参数后的所有参数
    --否则,selector应是字符串"#",以便函数select返回额外参数的总数
    
    function add(...)
        local s = 0
        for i=1,select("#", ...) do
            s = s + select(i, ...)
        end
        return s
    end
    
  2. 正确的尾调用

形如 return func(args)
它不会在调用堆栈上增加新的堆栈帧

10 模式匹配
  1. 相关函数
-- 1.string.find用于在指定的目标字符串中搜索指定的模式
string.find("hello", "l") --> 3 4

-- 2.和string.find相似,但他返回的是目标字符串中与模式相匹配的那部分子串,而不是该模式在的位置
string.match("hello", "llo") --> llo
string.match("today is 17/7/1990", "%d+/%d+/%d+") --> 17/7/1990

-- 3.string.gsub把目标字符串中出现模式的地方替换成字符串
string.gsub("lua is cute", "cute", "great", 1) --> lua is great (第四个参数限制次数

-- 4. string.gmatch返回一个函数,通过该函数可以遍历一个字符串中所有出现的指定模式
s = "some thing"
for w in string.gmatch(s, "%a+") do
    print(w)
end
--> some
--> thing
  1. 模式

1.字符分类

字符 含义 字符 含义
. 任意字符 %l 小写字母
%a 字母 %p 标点符号
%c 控制字符 %s 空白字符
%d 数字 %u 大写字母
%g 除空格外的可打印字符 %w 字母和数字
%x 十六进制数字

这些类的大写形式表示类的补集。

2.魔法字符

( ) . % + – * ? [ ] ^ $
以^开头的模式表示从目标字符串的开头开始匹配
以$结尾的模式表示匹配到目标字符串的结尾
(只有位于模式的开头和结尾才具有特殊意义,否则只是与自身相匹配的普通字符)

3.修饰符

字符 含义 字符 含义
+ 重复一次或多次 * 重复零次或多次
重复零次或多次(最小匹配) 可选(出现零次或一次)
15 数据文件和序列化

我们常常需要将某些数据序列/串行化,即将数据转换为字节流或字符流,以便将其存储到文件中或者通过网络传输。

16 编译、执行和错误

Lua中处理错误,可以使用pcall来包装需要执行的代码。pcall接收一个函数和要传递给后者的参数,并执行。执行结果:有错误、无错误;返回值true或者false,errorinfo。

if pcall(function_name, ...) then 
-- no error
else 
-- some error
end

pcall以一种“保护模式”来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。

17 模块和包

函数require在表package.loaded中检查模块是否已被加载。如果模块已经被加载,函数require就返回相应的值。因此,一旦一个模块被加载过,后续的对于同一模块的所有require调用都将返回同一个值,而不会再运行任何代码。

要强制函数require加载统一模块两次,可以先将模块从package.loaded中删除:
package.loaded.modname = nil

18 迭代器和泛型for

泛型for为一次迭代循环做了所有的记录工作:它在内部保存了迭代函数,因此不需要变量iter;它在每次做新的迭代时都会再次调用迭代器,并在迭代器返回nil时结束循环。

实际上,泛型for保存了三个值:一个迭代函数,一个不可变状态和一个控制变量。

    local function iter(t, i)
        i = i+1
        local v = t[i]
        if v then 
            return  i,v 
        end
    end

    function ipairs(t)
        return iter,t,0
    end
20 元表和元方法

元表可以修改一个值在面对一个未知操作时的行为。

算术运算相关的元方法:加法 __add,减法 __sub,乘法 __mul,除法 __div,负数 __unm,取模 __mod,幂运算 __pow。

位操作相关的元方法:按位与 __band,按位或 __bor,按位异或 __bxor,按位取反 __bnot,向左移位 __shl,向右移位 __shr,连接符行为 __concat。

关系运算相关的元方法:等于 __eq,小于 __lt,小于等于 __le。其他三个关系运算符没有单独的元方法,lua语言会将 a~=b 转换为 not (a == b) , a > b 转换为 b < a , a >= b 转换为 b <= a。

库定义相关的元方法:__tostring, __metatable, __pairs。

表相关的元方法:__index, __newindex。

21 面向对象(object-oriented)编程
Account = {balance = 0}
function Account:withdraw(v)
    self.balance = self.balance - v
end
  1. 类(Class)

类和原型都是一种组织多个对象间共享行为的方式。
为了创建其他与Account行为类似的账号,我们可以使用__index元方法让这些新对象从Account中继承这些操作。

function Account:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    return o
end
  1. 继承(Inheritance)
SpecialAccount = Account:new()
s = SpecialAccount:new{limit = 1000}

SpecialAccount就像继承其他方法一样从Account继承了new。

  1. 私有性(Privacy)

许多人认为,私有性是一门面向对象语言不可或缺的一部分:每个对象的状态都应该由它自己控制。

此前,我们所学习的Lua语言中标准的对象实现方式没有提供私有性机制。一方面,这是使用普通结构(表)来表示对象所带来的后果;另一方面,这也是Lua语言为了避免冗余和人为限制所采取的方法。

不过尽管如此,Lua语言还是可以用其他方式来实现具有访问控制能力的对象。

function newAccount(initalBalance)
    local self = {balance = initalBalance}
    local withdraw = function(v)
            self.balance = self.balance - v
        end

    return{
        withdraw = withdraw
    }
end
22 环境

Lua语言中处理全局变量的方式:
– 编译器在编译所有代码段前,在外层创建局部变量_ENV
– 编译器将所有自由名称var变换为_ENV.var;
– 函数load(或函数loadfile)使用全局环境初始化代码段的第一个上值,及Lua语言内部维护的一个普通的表。

23 垃圾收集
  1. 弱引用表

弱引用表、析构器和函数是在Lua语言中用来辅助垃圾收集器的主要机制。弱引用表允许收集Lua语言中还可以被程序访问的对象;析构器允许收集不在垃圾收集器直接控制下的外部对象;函数collectgarbage则允许我们控制垃圾收集器的步长。

弱引用表就是一种用来告知Lua语言一个引用不应阻止对一个对象回收的机制。所谓弱引用是一种不在垃圾收集器考虑范围内的对象引用。如果对一个对象的所有引用都是弱引用,那么垃圾收集器将会回收这个对象并删除这些弱引用。

一个表是否为弱引用表示由其元表中的__mode字段所决定的。”k”,”v”,”kv”

注意:只有对象可以从弱引用表中被移除,而像数字和布尔这样的“值”是不可回收的。

对于一个字符串类型的键来说,除非它对应的值被回收,否则是不会从弱引用表中被移除的。

  1. 垃圾收集器

i. Lua5.0及之前

一直到Lua5.0,Lua语言使用的都是一个简单的标记-清除式垃圾收集器。意味着会时不时停止主程序的运行来执行一次完整的垃圾收集周期。每个垃圾收集周期由四个阶段组成:标记、清理、清除和析构。

标记阶段把根节点集合标记为活跃。
清理阶段处理析构器和弱引用表。
清除阶段遍历所有对象。如果一个对象没有被标记为活跃,Lua语言就将其回收。否则,Lua语言清理标记,然后准备进行下一个清理周期。
最后再析构阶段,Lua语言调用清理阶段被分离出的对象的析构器。

ii. Lua5.1

Lua5.1使用了增量式垃圾收集器,这种垃圾收集器像老版的垃圾收集器一样执行相同的步骤,但是不需要在垃圾收集期间停止主程序的运行,相反,它与解释器一起交替运行。

iii. Lua5.2

Lua5.2引入了紧急垃圾收集。当内存分配失败时,Lua语言会强制进行一次完整的垃圾收集,然后再次尝试分配。

24 协程

从多线程的角度看,协程coroutine与线程thread类似:协程是一系列的可执行语句,拥有自己的栈、局部变量和指令指针,同时协程又与其他协程共享了全局变量和其他几乎一切资源。线程与协程的主要区别在于,一个多线程程序可以并行运行多个线程,而协程却需要彼此协作地运行,即在任意指定的时刻只能有一个协程在运行,且只有当正在运行的协程显式地要求被挂起时其执行才会暂停。

一个协程有以下四种状态,即挂起 suspended、运行 running、正常 normal和死亡 dead。

当协程A唤醒协程B时,协程A既不是挂起状态也不是运行状态,因为既不能唤醒协程A 同时正在运行的是协程,所以,协程A此时的状态就被称为正常状态。

Lua语言中有一个非常有用的机制是通过一对 resume-yield来交换数据。第一个resume函数会把所有的额外参数传递给协程的主函数:

co = coroutine.create(function(a,b,c)
        print("co",a,b,c+2)
    end)
cortoutine.resume(co,1,2,3) -> co 1 2 5 

Lua语言提供的是所谓的非对称协程,也就是说需要两个函数来控制协程的执行,一个用于挂起协程的执行,另一个用于恢复协程的执行。而其他一些语言提供的事对称协程,只提供一个函数用于在一个协程和另一个协程之间的切换控制权。

25 反射

反射是程序用来检查和修改自身某些部分的能力。像Lua语言这样的动态语言支持几种反射环境:

  • 环境允许运行时观察全局变量
  • 诸如type和require这样的函数允许运行时检查和遍历未知数据结构
  • 诸如load和require这样的函数允许程序在自身中追加代码或更新代码
Tags:

Add a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注