7. 迭代器和泛型for
迭代器是一种支持指针类型的结构,便利集合的每一个元素。Lua中常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。
闭包是一个内部函数,可以访问一个或者多个外部函数的局部变量。每次闭包的成功调用后,这些局部变量都保存他们的值,当然如果要创建一个闭包必须要创建其外部局部变量。一个典型的闭包的结构包含两个函数:一个闭包自己,另一个是工厂(创建闭包的函数)。
为list写一个简单迭代器,迭代器返回元素的值,而不是索引下标:
function list_iter(t)
local i = 0;
local n = table.getn(t);
return function ()
i = i + 1;
if i <= n thenreturn t[i] end;
end
end
list_iter是一个工厂,每次调用它都会创建一个新的闭包,闭包保存内部局部变量,每次调用它返回list的下一个元素值,list没有值时返回nil。
functionlist_iter(t)
local i = 0;
local n = table.getn(t);
return function ()
i = i + 1;
if i <= n thenreturn t[i] end;
end
end
t = {10, 20, 30};
iter = list_iter(t);
while true do
local element = iter();
if element == nil thenbreak end;
print(element);
end
-- 用于泛型for语句
t = {10, 20, 30}
for element in list_iter(t) do
print(element);
end
泛型for为迭代循环处理所有的簿记:首先调用迭代工厂;内部保留迭代函数,因此不需要iter变量,然后再每一个新的迭代处调用迭代函数。当迭代器返回nil时循环结束。
泛型for的语义:
for<var-list> in <exp-list>
<body>
end
<var-list>是一个或多个逗号分隔的变量名列表, <exp-list>是一个或多个以逗号分隔的表达式列表,exp-list通常只有一个值:迭代工厂的调用。
fork, v in pairs(t) do
print(k, v)
end
很多情况下变量列表只有一个变量,
forline in io.lines() do
io.write(line, ‘\n’);
end
变量列表中第一个变量为控制变量,其值为nil时循环结束。
泛型for的执行过程:
1.初始化,计算in后面表达式的值,表达式应该返回泛型for需要的三个值:迭代函数,状态常量和控制变量;如果表达式返回的结果个数不足三个,自动用nil补足。
2.将状态常量和控制变量作为参数调用迭代函数。
3.将迭代函数返回的值赋给变量列表
4.如果返回的第一个值为nil,循环结束,否则执行循环体
5.回到第二步再次调用爹哎函数。
forvar_1, … , var_n in explist do block end
等价于:
do
local_f, _s, _var = explist;
whiletrue do
localvar_1, …, var_n = _f( _s, _var);
_var= var_1;
if_var == nill then break end;
loop-block
end
end
无状态迭代器是指不保留任何状态的迭代器,利用无状态迭代器避免创建闭包话费额外的代价。
每一次迭代,迭代函数都用两个变量(状态变量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这个值获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,遍历数组的每一个元素。
a= {"one", "two", "three"};
for i, v in ipairs(a) do
print( i, v);
end
输出结果:
1 one
2 two
3 three
8. 编译,运行,调试
dofileloadfile 以及 loadstring将lua代码加载到Lua解释器进行运行。
其中loadfile只是将Lua代码加载到Lua解释器,并不运行,需要写运行语句进行运行。
require函数:
Lua提供高级的require函数来加载运行库:
require回搜索目录加载文件,require回判断是否文件已经加载,避免重复加载同一个文件
require使用的路径和普通的路径有些区别,一般看到的路径是一个目录列表,require路径是一个模式列表,每一个模式指明一种由需文件名转成文件名的方法。每一个模式是一个包含可选的问好的文件名,匹配的时候将问号用虚文件名替换,看是否存在这样的文件。
如?:?.lua;c:\windows\?;/usr/local/lua/?/?.lua
调用require “lili”时会试着打开这些文件
lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua
CPackages
C包在使用以前必须加载并连接,绝大多数系统中最容易的实现方式是动态链接库,然而动态链接库并不是ANSI C的一部分,在标准C中实现动态链接很困难。
Lua通常不包含任何不能用标准C实现的机制,动态链接库是一个特例。动态链接库机制是其他机制之母:拥有了动态链接机制,可以动态加载Lua中不存在的机制。Lua打破平台兼容的原则,通过条件编译方式为一些平台实现了动态链接机制。
Lua在一个loadlib函数内提供了所有的动态链接的功能,这个函数有两个参数:库的绝对路径和初始化参数,典型的调用例子如下:
localpath = “/usr/local/lua/lib/libluasocket.so”
localf = loadlib( path, “luaopen_socket”);
localpath = “/usr/local/lua/lib/libluasocket.so”
--or path = “C:\\windows\\luasocket.dll”
localf = assert(loadlib( path, “luaopen_socket”));
f() -- actually open the library
二进制的发布库包含一个与前面代码相似的stub文件,安装二进制库的时候,可以随便放在某个目录,只需要修改stub文件对应二进制库的世纪路径即可。将stub文件所在的目录加入到LUA_PATH,这样设定后可以使用require函数加载C库。
错误:
Lua遇到不期望的情况时回跑出错误,可以通过调用error函数显示的抛出错误,error的参数也是要抛出的错误信息。Lua提供了专门的内置函数assert来完成这个功能:
print“enter a number:”
n= assert( io.read(“*number”), “inlalid input”);
assert首先检查第一个参数的返回错误,如果不返回错误assert简单的返回,否则assert以第二个参数抛出错误信息。第二个参数是可选的。
遇到异常时,有两个基本的动作:返回错误代码或者抛出错误。有一个一般的原则:容易避免的异常应该抛出错误,否则返回错误代码。
如果在Lua中需要处理错误,使用pcall函数封装代码:
想运行一段Lua代码,这段代码运行过程中可以捕捉所有的异常和错误。
第一步:将代码封装在一个函数内:
functionfoo()
…
ifunexpected_condition then error() end
…
print(a[i]);
end
第二步:使用pcall调用这个函数
ifpcal(foo) then
… -- no errors while running ‘foo’
else
… -- ‘foo’ raised an error: take appropriateactions
end
pcall在保护模式下调用他的第一个参数并运行,因此可以补货所有的异常和错误。没有异常和错误,pcall返回true和调用返回的任何值:否则返回nil加错误信息。
错误信息不一定非要是一个字符串,传递给error的任何信息都会被pcall返回
localstatus, err = pcall( function() error ({code=121}) end);
print(err.code);
这种机制提供了在Lua中处理异常和错误所需要的全部内容,通过error抛出异常,然后通过pcall捕获。
当错误发生时,Lua会在栈释放以前调用错误处理函数,可以使用debug收集错误相关的信息,有两个常用的debug处理函数:debug和debug.traceback,前者给出Lua的提示符,可以手动查看错误发生时的情况,后者通过traceback创建更多错误信息。可以在任何时候,调用debug.traceback来获取当前运行的traceback信息:
print(debug.traceback())
9. 协同程序:
协同程序与多线程情况下的线程比较类似:有自己的堆栈,局部变量,指令指针,但是和其他的协同程序共享全局变量等很多信息。线程和协同程序的不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程,而协同程序则是通过协作来完成,在任一指定时刻只有一个协同程序在运行,运行的协同程序只有在明确被要求挂起时才会被挂起。
Lua通过table提供所有得协同函数,create函数创建一个新的协同程序,create只有一个参数,协同程序将要运行的代码封装而成的函数,返回值为thread类型的值表示创建一个新的协同程序。
co= coroutine.create(function () print(“hi”) end);
print(co);
协同有三个状态:挂起态,运行态,停止态。创建协同程序后为挂起状态,不会自动运行,使用status检查协同的状态。
coroutine.resume(co); 使得程序由挂起状态变为运行态。
corouting.yield(co); 使得运行的程序挂起
例如:
co = coroutine.create( function ()
for i = 1, 10 do
print("co",i);
coroutine.yield();
end
end);
coroutine.resume( co);
print(coroutine.status( co));
运行结果:
co 1
suspended
从协同的观点,使用函数yield可以使程序挂起,yield返回后继续程序的执行直到再次遇到yield或者程序结束,则停止。
coroutine.resume(co) --> co 2
coroutine.resume(co) --> co 3
…
coroutine.resume(co) --> co 10
resume运行在保护模式下,如果协同内部存在错误,并不会抛出错误而是将错误返回给resume函数。
Lua中一对resume-yield可以相互交换数据。
只有resume,没有相应的yield,resume把额外的参数传递给协同的主程序:
co = coroutine.create( function (a, b,c )
print("co",a, b, c);
end);
print( coroutine.resume( co, 1, 2, 3));
运行结果:
co 1 2 3
true
resume返回除了true以外的其他部分讲作为参数传递给yield
co = coroutine.create(function (a, b)
coroutine.yield(a + b, a - b);
end);
print( coroutine.resume(co, 20, 10));
true 30 10
对称性,yield返回的额外的参数也会传递给resume
co = coroutine.create( function ()
print("co",coroutine.yield());
end);
coroutine.resume( co);
coroutine.resume( co, 4, 5);
co 4 5
协同函数结束时,主函数返回的值都会传递给相应的resume:
co= coroutine.create( function ()
return6, 7;
end);
Lua提供的这种协同称为不对称的协同,挂起一个正在执行的协同的函数与使一个被挂起的协同再次执行的函数时不同的。提供对称的协同的语言,这种情况下由执行到挂起之间状态转换的函数是相同的。
管道和过滤器:
用作迭代器的协同
非抢占式多线程:
10. 完整示例
function fwrite (fmt, ...)
returnio.write(string.format(fmt, unpack(arg)));
end
function BEGIN()
io.write([[
<HTML>
<HEAD><TITLE>Projectsusing Lua</TITLE></HEAD>
<BODY BGCOLOR= "#FFFFFF">
Here are briefdescriptions of some projects around the world
that use <AHREF = "home.html">Lua</A>
]]);
end
function entry0 (o)
N = N + 1;
local title = o.title or'(no title)';
fwrite('<LI><AHREF = "#%d">%s</A>\n', N, title);
end
function entry1 (o)
N = N + 1;
local title = o.title oro.org or 'org';
fwrite('<HR>\n<H3>\n');
local href = '';
if o.url then
href = string.format("HREF=%s",o.url);
end
fwrite('<ANAME="%d"%s>%s</A>\n', N, href, title);
if o.title and o.orgthen
fwrite('\n<SMALL><EM>%s</EM></SMALL>',o.org);
end
fwrite('\n</H3>\n');
if o.description then
fwrite('$s',string.gsub(o.description, '\n\n\n*', '<P>\n'));
fwrite('<P>\n');
end
if o.email then
fwrite('Contact:<A HREF="mailto:%s">%s</A>', o.email, o.contact,o.email);
elseif o.contact then
fwrite('Contact:%s\n', o.contact);
end
end
function END()
fwrite('</BODY></HTML>\n');
end
BEGIN()
N = 0;
entry = entry0
fwrite('<UL>\n');
dofile('E:\\Lua\\Demo\\ProgramInLua\\db.lua');
fwrite('</UL>\n');
N = 0;
entry = entry1
dofile('E:\\Lua\\Demo\\ProgramInLua\\db.lua');
END()
将下面的table读出,并写入到文件中去:
entry = {
title ="Tecgraf",
org = "ComputerGraphics Technology Group, PUC-Rio",
url ="http://www.tecgraf.puc-rio.br/",
contact = "WaldemarCeles",
description = [[
TeCGraf is theresult of a partnership between PUC-Rio,
the PontificalCatholic University of Rio de Janeiro,
and <AHREF="http://www.petrobras.com.br/">PETROBRAS</A>,
the Brazilian OilCompany.
TeCGraf is Lua'sbirthplace,
and the languagehas been used there since 1993.
Currently, morethan thirty programmers in TeCGraf use
Lua regularly;they have written more than two hundred
thousand lines ofcode, distributed among dozens of
final products.]]
}
11. 数据结构
Lua中通过整数下标访问表中的元素即可简单的实现数组,数组不需事先制定大小,可以随需要动态增长。
初始化数组的时候,间接定义数组大小:
a= {}
for i = 1, 100 do
a[i] = 0
end
可以定义数组的下标从0,1或者其他的数值开始。Lua中习惯上下标从1开始,Lua标准库与此保持一致。
a = {}
for i = -5, 5 do
a[i] = 0
end
Lua中有两种方法表示矩阵,一种是用数组表示,一个表的元素是另一个表,例如创建n行m列的矩阵
mt= {}
fori = 1, N do
mt[i]= {}
forj = 1, M do
mt[i][j]= 0;
end
end
Lua中table是个对象,每一行必须显示创建一个table,
第二种方法是将行和列组合起来,如果索引下标都是整数,通过第一个索引乘以一个常量再加上第二个索引
mt= {}
fori = 1, N do
forj = 1, M do
mt[i*M + j] = 0;
end
end
列表:Lua中用tables很容易实现链表,每一个节点是一个table,指针式这个表的一个域,指向另一个节点。
根节点 list = nil
在链表开头插入一个值为v的节点:
list= {next = list, value = v};
遍历链表需要:
locall = list;
whilel do
print(l.value);
l= l.next;
end;
双端队列:Lua的table标准库提供了insert和remove操作,可以实现队列,但是这种方式效率过低,有效的方式是使用两个索引下标来实现:
functionListNew()
return{ first = 0, last = -1}
end
为了避免全局命名空间污染,重写代码,将其放入list的table表:
List= {};
functionList.new()
return{ first = 0, last = -1};
end
functionList.pushleft( list, value)
localfirst = list.first – 1;
list.first= first;
list[first]= value
end
functionList.pushright( list, value)
locallast = list.last + 1;
list.last= last;
list[last] = value
end
functionList.popleft( list)
localfirst = list.first;
iffirst > list.last then error(“list is empty”) end
localvalue = list[first]
list[first]= nil
list.first= first + 1;
end
functionList.popright( list)
locallast = list.last;
iflist.first > last then error(“list is empty”) end
localvalue = list[last];
list[last]= nil
list.last= last – 1;
returnvalue;
end
集合:例如要表示语言的关键词所组成的集合,以及判断一个单词是否属于关键词集合,对于Lua来说是一个简单有效的事情。
将所有的集合中的元素作为下标存放在table中,在测试是否属于集合时,只需要测试对于给定元素,表的对应下标的元素值是否为nil。
reserved= {
[“while”]= true, [“end”] = true, [“function”] =true, [“local”] = true
}
字符串缓冲:
如果将一个文件的字符串读进来,链接为一个大的字符串:
localbuff = “”
forline in io.lines() do
buff= buff .. line .. “\n”;
end
这是一个很正常的处理方法,但是Lua中这段代码的效率极低,不断地创建新的对象,时间就比较久。Lua专门提供了io.read(*all);来处理这个问题。
12. 数据文件和持久化
序列化:序列化一些数据,为了将数据转换为字节流或者描述符,可以保存到文件或者通过网络发送出去。通常使用varname=<exp>来保存全局变量。
function serialize(o)
if type(o) =="number" then
io.write(o);
else
...
end
对于字符串来说
if type(o) == "string" then
io.write("'",o, "'");
end
字符串包含了特殊字符,产生代码将不是有效Lua程序,可以使用下面方法解决:
iftype(o) == “string” then
io.write(“[[”,o, “]]”);
end
string标准库中提供了格式化函数,专门提供了”%q”选项,使用双引号表示字符串,并且正确处理包含的引号和换行符等字符串
function serialize(o)
if type(o) == "number"then
io.write(o);
elseif
io.write(string.format("%q",o));
else
...
end
对于表的处理:
elseiftype(o) == "table" then
io.write("{\n");
fork, v in pairs(o) do
io.write("[");
serialize(k);
io.write("]");
serialize(v)
io.write(",\n");
end
io.write("}\n");
else
error("cannotserialize a " .. type(o));
end
这样可以解决table的序列化,但是需要table的格式是固定的。
保存带有循环的表:
假设要保存的表只有一个字符串或者数字关键字:
function basicSerialize(o)
if type(o) =="number" then
returntostring(o);
else
return string.format("%q",o);
end
end
function save( name, value, saved)
saved = saved or {};
io.write(name, " =");
if type(value) =="number" or type(value) == "string" then
io.write(basicSerialize(value),"\n");
elseif
if saved[value]then
io.write(saved[value],"\n");
else
saved[value]= name;
io.write("{}\n");
for k, vin pairs(value) do
localfieldname = string.format("%s[%s]", name, basicSerialize(k))
save(fieldname,v, saved);
end
end
else
error("cannotsave a " .. type(value));
end
end
保存表,例如:
a= { x = 1, y = 2; { 3, 4, 5}};
a[2]= a;
a.z= a[1];
save(‘a’, a)之后结果为:
a= {}
a[1]= {}
a[1][1]= 3
a[1][1]= 4
a[1][1]= 5
13. Metatables 和Metamethods
Lua中的table由于定义的行为,可以对key-value键值对执行加操作,访问key对应的value,遍历key-value。不可以对两个表执行加操作,也不可以比较两个表的大小。
Metatables允许改变table的行为。例如Metatables可以定义Lua如何计算两个table的相加操作 a+b。当试图对两个表相加时,检查两个表是否有一个表有Metatables并检查是否有__add域,找到则调用这个__add函数计算结果。
使用setmetatable函数设置或改变一个表的metatable
t1= {}
setmetatable(t, t1);
assert(getmetatable(t)== t1)
任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable,一个表也可以是自身的metatable。
1. 算术运算的Metamethods:对表进行的操作,实现两个表的交差并补集合的运算
例如一个集合,如下:
Set = {}
function Set.new (t)
local set = {};
for _, l in ipairs(t) doset[l] =true end
return set;
end
function Set.union (a, b)
local res = Set.new{};
for k in pairs(a) dores[k] = true end
for k in pairs(b) dores[k] = true end
return res;
end
function Set.intersection (a, b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res;
end
帮助理解程序运行,定义打印函数输出结果
function Set.tostring (set)
local s = "{";
local sep = ""
for e in pairs(set) do
s = s .. sep ..e;
sep = ","
end
return s .."}";
end
function Set.print (s)
print( Set.tostring(s));
end
-- 想要加号运算符执行两个集合的并操作,将所有集合共享一个metatable
-- 定义一个普通表,用来做metatable,避免污染命名空间,放在set内部
Set.mt = {}
-- 修改Set.new函数,增加一行,创建表的时候指定对应的metatable
function Set.new (t)
local set = {}
setmetatable(set,Set.mt)
for _, l in ipairs(t) doset[l] = true end
return set;
end
-- 这样set.new创建的所有的集合都有相同的metatable了:
s1 = Set.new{ 10, 20, 30, 50};
s2 = Set.new{30, 1};
print(getmetatable(s1));
print(getmetatable(s2));
--给metatable增加__add函数
Set.mt.__add = Set.union
-- 当Lua师徒对两个集合相加时,将调用这个函数,以两个相加的表作为参数
s3 = s1 + s2;
Set.print(s3);
-- 同样的可以使用相乘运算符定义集合的交集操作
运行结果如下:
table: 003CC3B0
table: 003CC3B0
{1, 30, 10, 50, 20}
这种机制是实现类的基础,这样可以实现一个类的功能。
2.关系运算的Metamethods:
3.库定义的Metamethods
4.表相关的Metamethods
解决表的两种正常状态:表的不存在的域的查询和修改,Lua也提供了tables的行为的方法。
__indexMetamethods:访问一个表的不存在的域,返回结果为nil,这种访问出发lua解释器去查找__index metamethods:如果不存在返回nil,如果存在由__index metamethods返回结果。
一个原型是一种继承的例子:想创建一些表描述窗口,每一个表描述窗口一些参数,包括位置,大小,颜色等。这些参数都有默认的值,要创建窗口的时候只需要给胡非默认值得参数即可创建我们需要的窗口。一种方法是实现一个表的构造器,对表内的每一个缺少域都填上默认值。第二种方法是创建一个新的窗口去继承一个原型窗口的缺少域。
-- create a namespace
Windows = {}
-- create the prototype with default values
Windows.prototype = { x = 0, y = 0, width = 100, height = 100,}
--create a metatable
Windows.mt = {}
-- declare the constructor function
function Windows.new (o)
setmetatable( o,Windows.mt);
return o;
end
-- 定义__indexmetamethods
Windows.mt.__index = function ( table, key)
returnWindows.prototype[key];
end
--这样就可以创建一个新的窗口,访问缺少的域结果
w = Windows.new{x = 10, y = 20}
print(w.width);
Lua发现w不存在width域时,有一个metatable带有__index域,Lua使用w和width来调用__index metamethods,metamethods则通过访问原型表获取缺少的域的结果。
__indexmetamethods在继承中非常常见,Lua提供了一个更加简洁的使用方式,__index metamethods不需要非是一个函数,也可以是一个表。它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数,当他是一个表的时候,Lua将在这个表中看是否缺少的域。
使用这种方法改写:
Windows.mt.__index= Windows.prototype;
当Lua查找metatable的__index域时,发现windows.prototype的值,是一个表,Lua将访问这个表来获取缺少的值,相当于执行:
Windows.prototype[“width”]
将一个表作为__index metamethods使用,提供了一种廉价而简单的实现单继承的方法。
__newindexMetamethods:
__newindexMetamethods用来对表更新,__index则用来对表访问。
当给一个缺少的域赋值时,解释器就会查找__newindexmetamethod:如果存在则调用这个函数而不进行赋值操作。如果是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作。
__index和 __newindexmetamethods的混合使用提供了强大的结构:从只读表到面向对象编程带有继承默认值的表。
14. 环境
Lua用一个名为environment普通的表保存所有的全局变量,更精确地说,Lua在一系列的environment中保存他的global变量。
简化Lua的内部实现,所有的全局变量不一定要有一样的数据结构。再者可以像其他表一样操作这个保存全局变量的表。为了简化操作,Lua将环境本身存储在一个全局变量_G中。
for n inpairs(_G) do print(n) end
运行结果:
string
xpcall
package
tostring
os
unpack
require
getfenv
setmetatable
next
assert
tonumber
io
rawequal
collectgarbage
arg
getmetatable
module
rawset
math
debug
pcall
table
newproxy
type
coroutine
_G
select
gcinfo
pairs
rawget
loadstring
ipairs
_VERSION
dofile
setfenv
load
error
loadfile
使用动态名字访问全局变量:
赋值操作对于访问和修改全局变量已经足够。对于需要操作的一个名字被存储在另一个变量中的全局变量,或者需要在运行时才能知道的全局变量,使用如下方法实现:
loadstring(“value= ” .. varname); 或 value = loadstring(“return ” .. varname);
如果varname是x,则操作结果是 return x; 这段代码涉及到一个新的chunk的创建和编译,以及很多额外的问题,可以换一种搞笑简洁的完成同样功能。
value= _G[varname];
环境是一个普通的表,可以使用你需要获取的变量索引表即可。
表域可以使用如 io.read或是a.b.c.d的动态名字,循环解决这个问题:
functiongetfield(f)
local v = _G;
for w in string.gfind(f,"[%w]+") do
v = v[w];
end
return v;
end
设置一个域的函数稍微复杂: a.b.c.d.e = v;
必须记住最后一个名字,独立的处理最后一个域,新的setfield函数当其中的域不存在的时候需要创建中间表:
function setfield (f, v)
local t = _G;
for w, d instring.gfind( f, "([%w_]+)(.?)") do
if d =="." then -- not lastfield
t[w] =t[w] or {}; -- create table if absent
t = t[w];
else -- last field
t[w] = v; -- do the assignment
end
end
end
声明全局变量:
非全局的环境:
15. Packages
很多语言提供了某种机制组织全局变量的命名,每一种机制在对package中声明的元素的可见性以及其他一些细节使用不同的规则。都提供了一种避免不同库中命名冲突的问题的机制。
Lua中没有明确的机制实现packages,使用语言的基本机制实现,像标准库一样,使用表来描述package:
可以像使用其他表一样使用packages,可以使用语言所提供的所有的功能,带来便利。
简单方法:对包内的每一个对象都加报名作为前缀。例如假定写一个操作复数的库:使用表来表示复数,表有域r(实数部分)和i(虚数部分),在另一张表中声明我们所有的操作来实现一个包:
complex = {}
function complex.new ( r, i) return {r=r, i = i} end;
complex.i = complex.new(0, 1);
function complex.add (c1, c2)
return complex.new(c1.r+ c2.r, c1.i + c2.i);
end
function complex.sub (c1, c2)
return complex.new(c1.r- c2.r, c1.i - c2.i);
end
function complex.mul (c1, c2)
return complex.new( c1.r* c2.r - c1.i * c2.i, c1.r * c2.i + c1.i * c2.r);
end
function complex.inv (c)
local n = c.r^2 + c.i ^2;
return complex.new( c.r/ n, -c.i / n);
end
return complex
库定义了一个全局明:complex,其他的定义都在这个表内
可以使用符合规范的任何复数操作:
c= complex.add( complex.i, complex.new(10, 20));
这中用表实现的包和真正的包不完全相同。
对于每一个函数定义都必须显示在前面加包的名称。同一个包内的函数互相调用必须在被调用函数前指定包名。
可以使用固定的局部变量名来改善这个问题。
私有成员(Privacy)
包与文件:
一般的将包名加上”.lua”来命名对应文件。这样直接require包名即加载文件了。
对于先命名文件后命名package的,可以使用_REQUIREDNAME变量来重命名。记住,当require加载一个文件时,它定义了一个变量来表示虚拟的文件名:
local p = {}
if _REQUIREDNAME == nil then
complex = P;
else
_G[_REQUIREDNAME] = P;
end
使用全局表:
16. 面向对象程序设计
Lua中的表不仅是一种对象,像对象一样,表也有状态(成员变量),也有与对象的值独立的本性。一个对象在不同的时候可以有不同的值,但是始终是一个对象。表的生命周期与其由什么创建,在哪里创建没有关系。对象有他们的成员函数,表也有:
Account= {banlance = 0;}
functionAccount.withdraw (v)
Account.balance= Account.balance – v;
end
这个定义创建了一个新的函数,保存在Account对象的withdraw域内,可以这样调用:
Account.withdraw(100.00);
这种函数就是所谓的方法,但是在一个函数内使用全局变量名Account不是一个好习惯。这个函数只能在特殊的对象中使用,第二即使对这个特殊的对象而言,函数也只有在对象被存储的变量中才可以使用。
改变这个对象名字,函数withdraw将不能工作:
a= Account; Account = nil;
a.withdraw(100.00); -- 错误
这种行为违背了前面的对象应有独立的生命周期的原则。
一种灵活的方法是:定义方法的时候,带上一个额外的参数,表示方法作用的对象,这个参数经常为self或this
functionAccount.withdraw (self, v)
self.balance= self.balance – v;
end
当我们调用这个方法的时候,不需要指定它的操作对象了
a1= Account; Account = nil;
…
a1.withdraw(a1, 100.00);
使用了self参数定义函数之后,可以将这个函数用于多个对象上:
a2= {balance = 0, withdraw = Account.withdraw}
…
a2.withdraw(a2,260.00)
self参数的使用时很多面向对象的要点,大多数OO语言隐藏了这种机制,程序员不需要声明这个参数。Lua提供了通过使用冒号操作符来隐藏这个参数的声明。
functionAccount:withdraw ( v)
self.balance= self.balance - v
end
调用方法如下:
a.withdraw(100.00);
冒号的效果相当于在函数定义和函数调用的时候,增加了一个额外的隐藏参数。可以使用dot语法定义函数,而用冒号语法调用函数,反之亦然。
但是需要正确地处理好额外的参数。
Account = {
balance = 0,
withdraw = function(self, v)
self.balance =self.balance - v;
end
}
function Account:deposit (v)
self.balance =self.balance + v;
end
Account.deposit(Account, 200.00);
Account.withdraw(100.00);
现在的对象拥有了一个标示符,一个状态,和操作状态的方法。缺少class系统,继承和隐藏。
类:面向对象的语言中提供了类的概念,作为创建对象的模板。对象是类的实例。Lua中不存在类的概念,每个对象定义了自己的行为并拥有自己的形状。基于原型的语言,比如Self和NewtonScript,Lua中仿效类的概念不难。这些语言中对象没有类,相反,每个对象都有一个prototype,调用不属于对象的某些操作时,会最先到prototype中查找这个操作。
在这类语言中实现类的机制,创建一个对象,作为其他对象的原型即可(原型对象为类,其他的对象为类的instance)。类与prototype工作机制相同,定义特定对象的行为。
使用前面的击沉思想,实现prototypes,更明确地,如果有两个对象,a和b,想让b作为a的prototype只需要:
setmetatable(a,{__index = b});
这样对象a调用任何不存在的成员都会到对象b查找。术语上,将b看做类,a看做对象。为了使得新创建的对象拥有和Account相似的行为,使用__index metamethod,使新的对象继承Account。优化:不创建额外的表作为account对象的metatable,可以用Account表本身作为metatable
functionAccount : new ( o)
o= o or {}
setmetatable(o,self)
self.__index = self;
returno
end
创建一个新的账号,并调用一个方法的时候:
a= Account : new{ balance = 0}
a:deposit(100.00);
一个类不仅提供方法,也提供了它的实例的成员的默认值:第一个Account定义中,提供了balance默认值为0,创建一个新的账号而没有提供balance的初始值,将继承默认值:
b= Account : new()
print(b.balance);
继承:
面向对象语言中,继承使得类可以访问其他类的方法,在Lua中也可以实现:
Account = { balance = 0};
function Account:new (o)
o = o or {}
setmetatable(o, self);
self.__index = self;
return o;
end
function Account:deposit ( v)
self.balance =self.balance + v;
end
function Account:withdraw (v)
if v > self.balancethen error("insufficient funds") end;
self.balance =self.balance - v;
end
-- 从积累派生出一个子类SpecialAccount,这个子类允许客户取款超过它的存款余额
SpecialAccount = Account:new()
--奇妙的事情发生了:
s = SpecialAccount : new {limit = 1000.00};
-- SpecialAccount从Account继承了new方法,当new执行的时候,self参数指向SpecialAccount
-- 所以s的metatable是SpecialAccount,__index也是SpecialAccount
s:deposit(100.00);
-- Lua在s中找不到deposit域,会到SpecialAccount中查找,SpecialAccount中找不到,
-- 会在Account中查找。
-- SpecialAccount可以重定义从父类继承来的方法:
function SpecialAccount:withdraw (v)
if v - self.balance>= seft:getLimit() then
error("Insufficientfunds");
end
self.balance =self.balance - v;
end
function SpecialAccount:getLimit()
return self.limit or 0;
end
多重继承:
Lua中有很多方法实现面向对象的程序设计,所见到的使用indexmetamethods的方法是简洁,性能和灵活性各方面综合最好的。
在Lua中实现多继承,实现的关键在于:将函数用作__index。当一个表的metatable存在一个__index函数时,如果Lua调用一个原始表中不存在的函数,Lua将调用这个__index指定的函数。可以用__index实现多个父类中查找子类不存在的域。
多继承意味着一个类拥有多个父类,不能用创建一个类的方法去创建子类。定义一个特殊的函数createClass来完成,将被创建的新类的父类作为这个函数的参数,这个函数创建一个表来表示新类,并且将它的metatable设定为一个可以实现多继承的__index metamethods。尽管是多重继承,每一个实例亦然属于一个在其中能找到它需要的方法的单独的类。这种类和父类之间的关系与传统的类与实例的关系是有区别的。一个类不能同时是其实例的metatable,又是自己的metatable。
-- look up for 'k' in list of tables 'plist'
local function search( k, plist)
for i = 1,table.getn(plist) do
local v =plist[i][k];
if v then returnv end;
end
end
function createClass (...)
local c = {}; -- new class
-- class will search foreach method in the list of its
-- parents('arg' is thelist of parents)
setmetatable( c, {__index = function (t, k) return search(k, arg) end});;
-- prepare 'c' to be themetatable of its instances
c.__index = c;
-- define a newconstructor for this new class
function c:new(o)
o = o or {}
setmetatable( o,c);
return o;
end
-- return new class
return c;
end
Named = {}
function Named:getname()
return self.name;
end
function Named:setname(n)
self.name = n;
end
--为了创建一个继承与这两个类的新类,调用createClass
NamedAccount = createClass(Account, Named)
-- 为了创建和使用实例,通常这样:
account = NamedAccount:new{name = "Paul"};
print(account:getname())
-- Lua 在account中找不到getname,因此查找account的metatable的__index
-- 即NamedAccount。但是NamedAccount也没有getname,因此Lua查找NamedAccount的metatable
-- 的__index,这个域包含了一个函数,Lua调用这个函数并首先到Account中查找getname
-- 没有找到,然后到Named中查找,并最后返回结果。
-- 由于多重继承的搜素的复杂性,多重继承的侠侣比单继承要低。
私有性(privacy)
Single-Method的对象实现方法
17. Weak表
Lua自动进行内存的管理,程序只能创建对象,而没有执行删除对象的函数,通过使用垃圾收集技术,Lua会自动删除那些失效的对象。
垃圾收集器只能在确认对象失效后才会进行收集,因此应当自己处理它,为不用的对象赋值nil值,防止他们锁住其他的空闲对象
Weak表中记录了没有被引用的对象,这样GC机制就可以将这些对象进行回收。
记忆函数
关联对象属性
重述带有默认值的表