第三部分《Programming in Lua》语言特性¶
十八 迭代器和泛型 for¶
1.迭代器和闭包¶
- 迭代器:遍历一个集合中所有元素的代码结构
- 典型:io.read()
- 机制: 保存状态(闭包)
- 闭包结构 = 闭包本身 + 封装变量的工厂
- 举例,返回元素的值
在这个例子中,values 就是工厂。每当调用这个工厂时,它就会创建一个新的闭包(即迭代器本身)。这个闭包将它的状态保存在其外部的变量 t 和 i 中,这两个变量也是由 values 创建的。每次调用这个迭代器时,它就从列表 t 中返回下一个值。在遍历完最后一个元素后迭代器返回 nil,表示迭代结束。
function values (t) local i = 0 return function() i = i+1; return t[i] end end
可以利用 while 或者 for 来使用这个迭代器
2. 泛型 for 语法¶
保存了三个值: - 一个迭代函数 - 一个不可变状态 - 一个控制变量:变量列表第一个,永远不为 nil
for var-list in exp_list do
body
end
for k, v in pairs(t) do print(k, v) end
3.无状态迭代器(如ipairs)¶
特点: 1. 不保存任何状态 2. 多个循环使用同一个迭代器,避免创建新闭包的开销
ipairs示例:
lua 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
function pairs(t)
return next,t,nil
end
二十 元表和元方法¶
可以认为,元表是面向对象领域中的受限制类。像类一样,元表定义的是实例的行为
不过,由于元表只能给出预先定义的操作集合的行为,所以元表比类更受限。 同时,元表也不支持继承
- 元表可以修改一个值在面对一个未知操作时的行为。
- Lua 语言中的每一个值都可以有元表
- 每一个表和用户数据类型都具有各自独立的元表
- 其他类型共享对应类型所属的同一个元表
设置元表(在lua种,我们只能为表设置元表) 如果要为其他类型的值设置元表,则必须通过C代码或调试库完成
t = {} print(getmetatable(t)) -->nil
t1 = {}
setmetable(t,t1)
print(getmetatable(t) == t1) -->true
二十一 面向对象编程¶
1.对象¶
Lua一张表即为对象,拥有一个与其值无关的标识self。
2.方法(self,冒号操作符)¶
-
全局名称(糟糕,只能针对特定对象使用)
function Account.withdraw(v) Account.balance = Account.balance - v end
-
指定对象
function Account.withdraw(self,v) self.balance = self.balance - v end --使用 a1.withdraw(a1,100.00)
使用参数self是所有面向对象语言的核心点,大多数面向对象语言都隐藏这个机制。Lua语言同样可以使用冒号操作符隐藏
function Account:withdraw(v)
self.balance = self.balance - v
end
3. 类Class¶
Lua语言中没有类的概念,截至目前,我们的对象具有了标识、状态和对状态进行的操作。但还缺乏类体系,继承和私有性
-
利用元表来实现原型
在此之后,A就会在B中查找所有它没有的操作。可以把B看作对象A的类。setmetatable(A,{__index==B})
-
或者就直接使用__index元方法实现原型继承. a创建的时候自动将mt作为其元表,deposit在a找不到则会自动在__index中寻找
local mt = {__index = Account} function Account.new(o) o = o or{} setmetable(o,mt) return o end --- 创建新账户调用方法 a = Account.new(balance = 0) a:deposit(100.00)
-
改进
- 不创建扮演元表角色的表而是把表直接Account用作元表
- 第二种改进,对new方法也使用冒号语法。
function Account:new(o) o = o or {} self.__index = self setmetatable(o,self) return o end
继承¶
由于类也是对象,因此它们也可以从其他类获得方法,这种行为使得继承可以很容易在Lua语言中实现.
- Lua语言中有一个有趣的特性:
- 无需为了指定新行为而创建新类。如果一个函数withdraw里面实现了原型的getlimit方法。想要修改则直接在类里面重新设置getlimit方法就好了
多重继承¶
__index
只是实现继承的一种方法,是最均衡的做法。
当然,还有其他方法。
createClass¶
定义一个独立的函数来创建子类,设置新类元表中的元方法__index,由元方法来实现多重继承。 虽然是多重继承,每个实例仍属于单个类,并在其中查找所有的方法。
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})
-- 将'c'作为其实例的元表
c.__index = c
-- 为新类定义一个新的构造函数
function c:new(o)
o = o or {}
setmetatable(0,c)
return o
end
return c
end
---使用
NamedAccount = createClass(Account,Named)
- 也可以将继承方法移动到子类中,可以达到访问局部方法的速度,但是这样就不好修改方法定义了。
4. 私有性(信息隐藏)¶
Lua并没有提供私有性机制, * 一方面这是使用普通结构(表)来表示对象所带来的后果。 * 一方面为了避免冗余和人为限制所采取的方法,程序员不想访问就不要去访问 * 一种常见的作法就是把所有私有名称的最后都加上一个下画线
尽管如此,Lua语言的另外一项设计目标就是灵活性,提供能够模拟许多不同机制的元机制。 * 基本思想:两个表来表示一个对象,一个存状态,一个存操作(接口),通过第二个表来访问对象本身。第一个表则为私有表 * 这是通过设置函数内部局部变量和方法+返回方法名称实现私有 * 而不放入接口中的函数就是私有方法
function newAccount(initialBalance)
local self = {balance = initialBalance}
local withdraw = function(V)
self.balance = self.balance - v
end
local deposit = function(v)
self.balance = self.balance + v
end
local getBalance = function return self.balance end
return{
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance
}
end
5.单方法对象¶
使用单方法对象可以不用创建接口表,只用将这个单独的方法以对象的表现形式返回即可 * 举例:一个在内部保存了状态的迭代器就是一个单方法对象 * 另一种情况:一个根据不同的参数完成不同任务的分发方法(这样其实很高效,) * 每个对象使用一个闭包,要比使用一个表的开销更低 * 虽然不能继承,但可以拥有完全的私有性:访问单方法对象中某个成员只能通过该对象的唯一方法。
function newObject(value)
return function(action,v)
if action == "get" then return value
elseif action == "set" then value = v
else error("invalid action")
end
end
end
---use
d = newObject(0)
print(d("get"))
d("set",10)
6.对偶表示¶
实现私有性的另一种方式:表当作键,同时又把对象当作表的键
table[key] = value
key = {}
key[table] = value
举例:银行账户 好处在于即便能访问withdraw,除非能同时访问balance,否则也不能访问余额
local balance = {}
Account = {}
function Account.withdraw(self,v)
balance[self] = balance[self] - v
end
缺陷¶
一旦这样做,那么这个账户对于垃圾收集器而言永远不会变为垃圾,直到显式删除
优点¶
无须修改即可实现继承
二十二 环境¶
Lua语言不使用全局变量,但又不遗余力地进行模拟。
第一种近似模拟是把全局变量保存在_G表中。因此_G._G = _G
1.具有动态名称的全局变量¶
一般,赋值操作对于访问和设置全局变量已经够了。然而有时也需要某些形式的元编程。
value = _G[varname]
_G["io.read"]
,显然不能从表io中得到字段read的。但我没可以编写一个函数getfield让getfield("io.read")返回想要的结果。这个函数主要是一个循环,从_G开始逐个字段地进行求值
function getfield(f)
loval v = _G
for w in string.gmatch(f,"[%a_][%w_]") do
v = v[w]
end
return v
end
2.全局变量的声明¶
lua本身变量声明即可访问,但我们要避免错误的访问不存在的全局变量该怎么做呢?
setmetable(_G,{
__newindex = function(_,n)
error("attemp to write undeclared"..n,2)
end,
__index = function(_,n)
error("attemt to read undeclared variable"..n,2)
end,
})
那么如何声明呢? 方法一:rawset,绕过元方法
function declare(name,initval)
rawset(_G, name, inirval of false)
end
方法二:使用元方法允许全局变量=nil的赋值
二十四 协程¶
Lua协程特点: + 可以颠倒调用者和被调用者的关系 + 拥有自己的栈、局部变量和指令指针 + 与其他协程共享了全局变量和几乎其他的一切资源 + 任意指定的时刻之恩那个有一个协程运行,且只有当正在运行的协程显示地要求被挂起(suspend)是其执行才会暂停。
1.协程基础¶
协程四种状态: + 挂起suspended:创建时默认挂起 + 运行running + 正常:normal + 死亡:dead lua所有协程函数都放在表coroutine中。 + create()函数:创建协程,参数为协程体body(通常为匿名函数),返回一个thread类型值 + resume()启动:挂起->运行。死亡后再度唤醒会返回false及错误信息 + 真正强大之yield():让一个运行中的协程挂起自己,然后在后续恢复运行 + 从协程角度:挂起期间的活动都发生在调用yield期间。 + 唤醒的时候yield才会最后返回 + 协程会继续执行直到遇到下一个yield或结束
3.非全局环境¶
一个自由名称是指没有关联到显示声明上的名称,即它不出现在对应局部变量的范围内。例如,在下面的代码段中,x和y是自由名称。
loacl z = 10
x = y + z
local z = 10
_ENV.x = _ENV.y + z
那么_ENV变量又究竟是什么呢?
Lua语言把所有代码段都当作匿名函数。所以,Lua语言编译器实际上将原来的代码段编译为如下形式
local _ENV = some value(某些值)
return function(...)
local z = 10
_ENV.x = _ENV.y + z
end
_ENV
的初始值可以是任意的表。任何一个这样的表都称为一个环境
+ 为了维持全局变量存在的幻觉,lua语言在内部维护了一个表来用全局环境。
local _ENV = the global enviroment(全局环境)
return function(...)
local z = 10
_ENV.x = _ENV.y + z
end
阶段总结¶
lua处理全局变量的方式: + 编译器在编译所有代码前,在外层创建局部变量_ENV; + 编译器将所有自由名称var变换为_ENV.var; + 函数load(或者loadfile)使用全局环境初始化代码段的第一个上值,即lua语言内部维护的一个普通的表
4.使用_ENV¶
由于_ENV只是个普通变量,因此可以对其赋值或者像访问其他变量一样访问它。