您的位置:首页 > 编程语言 > Lua

lua程序设计第二版 读书笔记(11-14章)

2013-05-11 15:14 489 查看
书本下载地址 http://download.csdn.net/detail/myy2012/5349646
本部分下载地址 http://download.csdn.net/detail/myy2012/5355935

lua程序设计第二版 读书笔记(1-4章)

第一章 开始

第二章 类型与值

第三章 表达式

第四章 语句

/article/7932654.html

lua程序设计第二版 读书笔记(5-8章)

第五章 函数

第六章 深入函数

第七章 迭代器与泛型for

第八章 编译执行与错误

/article/7932655.html

lua程序设计第二版 读书笔记(9-10章)

第九章 协同程序

第十章 完整的实例

/article/7932656.html

lua程序设计第二版 读书笔记(11-14章)

第十一章 数据结构

第十二章 数据文件与持久性

第十三章 元表metatable与元方法meatmethod

第十四章 环境

/article/7932657.html

lua程序设计第二版 读书笔记(15-17章)

第十五章 模块与包

第十六章 面向对象编程

第十七章 弱引用 table

/article/7932658.html

lua程序设计第二版 读书笔记(18-21章)

第十八章 数学库

第十九章 table库

第二十章 字符串库

第二十一章 IO库

/article/7932659.html

lua程序设计第二版 读书笔记(22-23章)

第二十二章 操作系统库

第二十三章 调试库

/article/7932660.html

第十一章 数据结构

table本身 就比数据和列表的功能强大的多,因此许多算法都可以忽略一些细节问题,从而简化它们的实现。

11.1数组

使用整数来索引table即可在Lua中实现数组。例如:

a={}

for  i = -5, 5 do

a[i]=0

end


然而,在Lua中的习惯一般是以1为数组的起始索引,Lua库和长度操作符都遵循这个预定。(如果你的数组不是从1开始的,那就无法使用这些功能了)

11.2 矩阵与多维数组

在Lua中,有2中方式来表示矩阵。

第一种:使用“数组的数组”,即一个table中的每个元素是另一个table

例如:

  mt={}
    for  i=1, N do
     for  j=1, M do
Mt[i][j] = 0
  end
end


第二种:将2个索引合并为一个索引。

例如:

   mt={}
         for i=1, N do
   for j=1, M do
  Mt[(i-1)+j]=0
end
  end


稀疏矩阵:大多数元素为0或nil。

11.3链表

由于table是动态的实体,所以在Lua中实现链表是很方便的。每个节点以一个table来表示,一个“链接”只是结点table中一个字段,该字段包含了对其他table的引用。

例如:

  list = nil
list ={ next = list, value = v}
    local l=list
     <访问 l.value>
    l=l.next
 end


11.4 队列与双向队列

List={}
function List.new()
return {first=0, last=-1}
end


--------------

function List.pushfirst(list, value)
local first=list.first+1
list.first=first
list[first]=value
end
function List.pushlast(list, value)
local last=list.last-1
list.last=last
list[last]=value
end


---------------

function List.popfirst(list)
local first=list.first
if first<list.last then
error("list is empty")
end
local value=list[first]
list[first]=nil
list.first=first-1
return value
end
function List.poplast(list)
local last=list.last
if list.first<last then
error("list is empty")
end
local value=list[last]
list[last]=nil
list.last=last+1
return value
end


11.5 集合与无序组(bag)

将集合元素作为索引放入一个table中,那么对于任意值都无须搜索table,只需用该值来索引table,并查看结果是否为nil。

包,有时候也称为“多重集合”,与普通的集合不同之处在于每个元素可以出现多次。

11.6字符串缓冲

在Lua中,我们可以使用函数table.concat将一个table作为字符串缓冲,它会将给定列表中的所有字符串连接起来,并返回连接的结果。

例如

    local t={}
          for  line in io.lines() do
  t[#t + 1] = line
  end
  local s = table.concat(t, “\n”)..”\n”
  
  a={"good", "girl", "bad", "boy"}
  local s=table.concat(a)
  
  for k, v in ipairs(a) do
  	print(k, v)
  end
  print(s)	--goodgirlbadboy


11.7图

接下来介绍一种简单的面向对象的实现:结点表示为对象、边表示为结点间的引用。

--根据给定的名称返回对应的结点

local function name2node(graph, name)
if not graph[name] then
graph[name]={name=name, adj={}}
end
return graph[name]
end


-- 构造图

function readgraph()
local graph={}
for line in io.lines() do
local namefrom, nameto = string.match(line, "(%S+)%s+(%S+)")
local from = name2node(graph, nameto)	-- 查找相应的结点
from.adj[to] = true 	-- 将“to“添加到”from“的邻接集合
end
return graph
end


-- 函数findpath采用深度优先遍历算法,在两个结点间搜索一条路径

-- 第一个参数是当前结点,第二个参数是目标节点,

-- 第三个参数用于保存从起点到当前结点的路径,第四个参数是已访问结点的集合

-- 【注意】该算法直接对结点进行操作,而不是它们的名称

function findpath (curr, to, path, xisited)
path = path or {}
visited = visited or {}
if visited[curr] then		--结点是否已访问过?
return nil 				--这里没有路径
end
visited[curr] = true		--将结点标记为已访问
path[#path + 1] = curr		--将其加到路径中
if curr == to then 			--最后的结点吗?
return path
end
for node in pairs(curr.adj) do 		--尝试所有的邻接结点
local p = findpath(node, to, path, visited)
if p then
return p
end
end
path[#path] = nil			-- 从路径中删除节点
end


-- 测试上面所列函数

function printpath(path)
for i=1, #path do
print(path[i].name)
end
end

g = readgraph()
a = name2node(g, "a")
b = name2node(g, "b")
p = findpath(a,b)

if p then
printpath (p)
end


第十二章 数据文件与持久性

12.1 数据文件

将数据作为Lua代码来输出,当运行这些代码时,程序也就读取了数据。而table的构造式可以使这些输出代码看上去更像是一个普通的数据文件。

CSV(Comma-Separated Values 逗号分隔值):利用构造式作为格式。

自描述的数据(self-describing data):每项数据都伴随一个表示其含义的简短描述。

Lua不仅运行速度快,而且编译速度快。这不是偶然的结果,自从Lua创建之初就把数据描述作为Lua的主要应用之一来考虑的,开发人员能较快地编译大型程序投入了更多的努力。

12.2串行化

通常需要串行化一些数据,也就是将数据转换为一个字节流或字符流,然后就可以将其存储到一个文件中,或者通过网络连接发送出去了。串行化后的数据可以用Lua代码来表示,这样当运行这些代码时,存储的数据就可以的读取程序中得到重构了。

编写创建一个值的代码,方法如下:

function serialize(o)
  if type(o) == “number” then
  	io.write(o)
  elseif type(o) == “string” then
  	io.write(string.format(“%q”, o))
  else
  <其他情况>
  end
end


可以使用一个简单且安全的方法来括住一个字符串,那就是以“%q”来使用string.format 函数。这样它就会用双引号来括住字符串,并且正确地转移其中的双引号和换行符等其他特殊字符。

例如:

s=’a”problematic”\\string’
print(string.format(“%q”, s))  --"a\"problematic\"\\string"


Lua 5.1还提供了另一种可以以一种安全的方法来括住任意字符串的方法:[=[...]=]

--查找最长的等号序列

function quote(s)
local n=-1
for w in string.gmatch(s, "]=*") do
n=math.max(n, #w-1)
end
local eq=string.rep("=", n+1)
return string.format(" [%s[\n%s]%s] ", eq, s, eq)
end
print(quote("good]=girl bad=boy"))


保存无环的table

保存有环的table

function basicSerialize(o)
if type(o) == "number" then
return tostring(o)
else
return string.format("%q", o)
end
end
print(basicSerialize(45))
print(basicSerialize('three'))


第十三章 元表(metatable)与元方法(meatmethod)

可以通过元表来修改一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。例如:a和b都是table,通过元表可以定义如何计算表达式a+b。

当Lua试图将两个table相加时,它会先检查两者之一是否有元表,然后检查该元表中是否有一个叫_add的字段;如果找到该字段,就调用该字段对应的值(即元方法)。

Lua中的每个值都有一个元表。Table和userdata可以有各自独立的元表,而其他类型的值则共享所属的单一元表。

标准的字符串程序库为所有的字符串都设置了一个元表

  t={}
  print(getmetatable(t)) --nil
  print(getmetatable(12)) --nil
  print(getmetatable("hi")) --table: 0042EB20
  
  t1={}
  setmetatable(t, t1)
  print(getmetatable(t)) --table: 0063B378


13.1算术类元表

假设使用加号(+)来计算两个集合的并集,那么需要让所有用于表示集合的table共享一个元表,并且在该元表中定义如何执行一个加法操作。

算术类元方法: 字段:__add __mul __ sub __div __unm __mod __pow (__concat)

tA = {1, 3}
tB = {5, 7}
mt = {}
setmetatable(tA, mt)
 
function union(t1, t2)
 for _, item in ipairs(t2) do
   table.insert(t1, item)
 end
 return t1
end


  mt.__add = union
  tAB = tA + tB
  for k, v in pairs(tAB) do
      print(v)	--1 3 5 7
  end


13.2 关系类的元方法

字段:__eq(==)、 __lt(<)、 __le(<=),其他Lua自动转换 a~=b --> not(a == b) a > b --> b < a a >= b --> b <= a

例如:比较集合大小 <

  tA, tB = {3}, {1, 2}
  mt = {}
  function lessthan(tA, tB)
      return #tA < #tB
  end
  
  mt.__lt=lessthan
  
  setmetatable(tA, mt)
  setmetatable(tB, mt)
  
  print(tA < tB)	--true


与算术类的元方法不同的是,关系类的元方法不能应用于混合的类型。对于混合类型而言,关系类元方法的行为就模拟这些操作在Lua中普通的行为。如果试图将一个字符串与一个数字作顺序性比较,Lua会引发一个错误;同样,如果试图比较两个不同元方法的对象,Lua也会引发一个错误。

13.3 库定义的元方法

13.4 table访问的元方法

算术类和关系类运算符的元方法都为各种错误情况定义了行为,它们不会改变语言的常规行为。但是Lua还提供了一种可以改变table行为的方法。有两种可以改变的table行为:查询table及修改table中不存在的字段。

当访问一个table中不存在的字段时,得到的结果为nil。这是对的,但并非完全正确;实际上,这些访问会促使解释器去查找一个叫__index的元方法。如果没有这个元方法,那么访问结果如前述的为nil;否则,就由这个元方法来提供最终结果。

__index: 当访问table中不存在的字段时,得到的结果为nil。

Window = {}
Window.prototype = {x = 0, y = 0, width = 100, height = 100}
Window.mt = {}  --Window的元表

function Window.new(o)
setmetatable(o, Window.mt)
return o
end


--将Window的元方法__index指向一个匿名函数

--匿名函数的参数table和key取自于table.key。

Window.mt.__index = function(table,key)
  return Window.prototype[key]
end


--下面是测试代码:

w = Window.new{x = 10, y = 20}
print(w.width)   --100
print(w.height)   --100
print(w.width1)  --nil


说明:将一个table作为__index元方法是一种快捷、实现单一继承的方式。__index来实现相同功能的开销比较大,但方式更加灵活。

__newindex元方法:该元方法用于不存在键的赋值。当对一个table中不存在的索引赋值时,解释器就会查找__newindex元方法。如果有这个元方法,解释器就调用它,而不是执行赋值;如果这个元方法是一个table,解释器就在此table中执行赋值,而不是对原来的table。

具有默认值的table:缺省情况下,table的字段默认值为nil。但是我们可以通过元表修改这个默认值。

function setDefault(table,default)
local mt = {__index = function() return default end }
setmetatable(table,mt)
end
tab = {x = 10, y = 20}
print(tab.x,tab.z)  --10    nil
setDefault(tab,0)
print(tab.x,tab.z)  --10    0


跟踪table的访问:__index和__newindex都是在table中没有所需访问的index时才发挥作用的。因此,为了监控某个table的访问状况,我们可以为其提供一个空table作为代理,之后再将__index和__newindex元方法重定向到原来的table上。

t = {}        --原来的table
local _t = t    --保持对原有table的私有访问。
t = {}        --创建代理
--创建元表
local mt = {
__index = function(table,key)
print("access to element " .. tostring(key))
return _t[key]  --通过访问原来的表返回字段值
end,

__newindex = function(table,key,value)
print("update of element " .. tostring(key) .. " to " .. tostring(value))
_t[key] = value  --更新原来的table
end
}
setmetatable(t,mt)


t[2] = "hello"
print(t[2])


--输出结果为

--update of element 2 to hello

--access to element 2

--hello

只读的table:

通过代理的概念,可以很容易的实现只读table。只需跟踪所有对table的更新操作,并引发一个错误即可。

第十四章 环境

环境:Lua将其所有的全局变量保存在一个常规的table(环境)中。

14.1具有动态名字的全局变量

正因为环境是一个常规的table,才可以使用一个key(变量名)去直接索引它。类似地,还可以动态地计算出一个名称,然后将一个值赋予具有该名称的全局变量。

例如:_G[varname] = name

14.2全局变量声明

Lua中的全局变量不需要声明就可以使用。尽管很方便,但是一旦出现笔误就会造成难以发现的错误。我们可以通过给_G表加元表的方式来保护全局变量的读取和设置,这样就能降低这种笔误问题的发生几率了。

14.3. 非全局的环境:

全局环境存在一个刚性的问题,即它的修改将影响到程序的所有部分。Lua 5为此做了一些改进,新的特征可以支持每个函数拥有自己独立的全局环境,而由该函数创建的closure函数将继承该函数的全局变量表。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: