<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>凉风集 &#187; Lua</title>
	<atom:link href="http://www.lifangjin.com/archives/tag/lua/feed" rel="self" type="application/rss+xml" />
	<link>http://www.lifangjin.com</link>
	<description>李方进的个人BLOG</description>
	<lastBuildDate>Sun, 05 Feb 2012 15:36:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>尝试在delphi开发过程中使用LUA进行扩展</title>
		<link>http://www.lifangjin.com/archives/351</link>
		<comments>http://www.lifangjin.com/archives/351#comments</comments>
		<pubDate>Wed, 13 Jun 2007 07:27:04 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Delphi]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=351</guid>
		<description><![CDATA[使用的是lua for delphi扩展，文件共有5个，一个dll，4个pas。 lua版本是5.0.2，可以点击这里下载。 写了一个简单的demo，delphi主程序完成对lua扩展的加载，同时对用户输入的日期以及当前日期进行比较，lua文件完成对比较结果的文字化输出。代码如下： test.lua function dayinfo(num) local strdayinfo; if (num==0) then strdayinfo=&#34;今天&#34;; elseif (num&#38;gt;0) then strdayinfo=num..&#34;天前&#34;; elseif (num&#38;lt;0) then strdayinfo=math.abs(num)..&#34;天后&#34;; end print(strdayinfo); end delphi主程序 unit Unit1; &#160; interface &#160; uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,DateUtils; &#160; type TForm1 = class(TForm) Label1: TLabel; Button1: TButton; Edit1: TEdit; Memo1: TMemo; [...]]]></description>
			<content:encoded><![CDATA[<p>使用的是lua for delphi扩展，文件共有5个，一个dll，4个pas。<br />
lua版本是5.0.2，可以<a href="http://www.lifangjin.com/wp-content/uploads/2007/06/lua4delphi.zip" title="lua4delphi.zip">点击这里</a>下载。<br />
写了一个简单的demo，delphi主程序完成对lua扩展的加载，同时对用户输入的日期以及当前日期进行比较，lua文件完成对比较结果的文字化输出。代码如下：<br />
test.<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">lua</a></p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">function dayinfo(num)</li>
<li>local strdayinfo;</li>
<li>if (num==0) then</li>
<li>strdayinfo=&quot;今天&quot;;</li>
<li>elseif (num&amp;gt;0) then</li>
<li>strdayinfo=num..&quot;天前&quot;;</li>
<li>elseif (num&amp;lt;0) then</li>
<li>strdayinfo=math.abs(num)..&quot;天后&quot;;</li>
<li>end</li>
<li>print(strdayinfo);</li>
<li>end</li></ol></div>
<p><span id="more-351"></span><br />
delphi主程序</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">unit Unit1;</li>
<li>&nbsp;</li>
<li>interface</li>
<li>&nbsp;</li>
<li>uses</li>
<li>Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,</li>
<li>Dialogs, StdCtrls,DateUtils;</li>
<li>&nbsp;</li>
<li>type</li>
<li>TForm1 = class(TForm)</li>
<li>Label1: TLabel;</li>
<li>Button1: TButton;</li>
<li>Edit1: TEdit;</li>
<li>Memo1: TMemo;</li>
<li>procedure FormCreate(Sender: TObject);</li>
<li>procedure Button1Click(Sender: TObject);</li>
<li>private</li>
<li>{ Private declarations }</li>
<li>public</li>
<li>{ Public declarations }</li>
<li>end;</li>
<li>&nbsp;</li>
<li>var</li>
<li>Form1: TForm1;</li>
<li>&nbsp;</li>
<li>implementation</li>
<li>&nbsp;</li>
<li>{$R *.dfm}</li>
<li>uses</li>
<li>lua, lualib, lauxlib, LuaUtils;</li>
<li>&nbsp;</li>
<li>procedure TForm1.FormCreate(Sender: TObject);</li>
<li>begin</li>
<li>Edit1.Text:=datetostr(date());</li>
<li>//Edit1.Text:=inttostr(DayOfTheYear(date()));</li>
<li>end;</li>
<li>&nbsp;</li>
<li>function LuaPrint(L: Plua_State): Integer; cdecl;</li>
<li>var</li>
<li>I, N: Integer;</li>
<li>begin</li>
<li>//LuaShowStack(L, 'fobOp:LuaPrint ?n?');</li>
<li>&nbsp;</li>
<li>N := lua_gettop(L);</li>
<li>for I := 1 to N do</li>
<li>Form1.Label1.caption:=lua_tostring(L, I);</li>
<li>Result := 0;</li>
<li>end;</li>
<li>&nbsp;</li>
<li>procedure TForm1.Button1Click(Sender: TObject);</li>
<li>var</li>
<li>L: Plua_State;</li>
<li>begin</li>
<li>// Label1.Caption:= inttostr(DayOfTheYear(date())-DayOfTheYear(strtodate(Edit1.Text)));</li>
<li>&nbsp;</li>
<li>Memo1.Lines.LoadFromFile('test.lua');</li>
<li>Memo1.Lines.Add('dayinfo('+inttostr(DayOfTheYear(date())-DayOfTheYear(strtodate(Edit1.Text)))+')');</li>
<li>L := lua_open;</li>
<li>try</li>
<li>luaopen_math(L);</li>
<li>LuaRegister(L, 'print', LuaPrint);</li>
<li>LuaLoadBuffer(L, Memo1.Text, 'code');</li>
<li>LuaPCall(L, 0, 0, 0);</li>
<li>finally</li>
<li>lua_close(L);</li>
<li>end;</li>
<li>end;</li>
<li>end.</li></ol></div>
<p>经测试成功，程序会在label1中根据用户输入的日期来输出是今天，还是n天前或者n天后。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/351/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》18.数学库</title>
		<link>http://www.lifangjin.com/archives/293</link>
		<comments>http://www.lifangjin.com/archives/293#comments</comments>
		<pubDate>Mon, 05 Mar 2007 14:03:46 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=293</guid>
		<description><![CDATA[在这一章中(下面关于标准库的几章中同样)我的主要目的不是对每一个函数给出完整地说明，而是告诉你标准库能够提供什么功能。为了能够清楚地说明问题，我可能会忽略一些小的选项或者行为。主要的思想是激发你的好奇心，这些好奇之处可能在参考手册中找到答案。 数学库由算术函数的标准集合组成，比如三角函数库(sin, cos, tan, asin, acos, etc.),幂指函数(exp, log, log10), 舍入函数(floor, ceil), max, min,加上一个变量 pi.数学库也定义了一个幂操作符 ′^′ 。 所有的三角函数都在弧度单位下工作。(Lua4.0以前在度数下工作。)你可以使用deg和rad函数在度和弧度之间转换。如果你想在degree情况下使用三角函数，你可以重定义三角函数： local sin, asin, &#8230; = math.sin, math.asin, &#8230; local deg, rad = math.deg, math.rad math.sin = function (x) return sin(rad(x)) end math.asin = function (x) return deg(asin(x)) end &#8230; math.random用来产生伪随机数，有三种调用方式： 第一：不带参数，将产生 [0,1)范围内的随机数. 第二：带一个参数n，将产生1 &#60;= x &#60;= n范围内的随机数x. 第三：带两个参数a和b,将产生a [...]]]></description>
			<content:encoded><![CDATA[<p>在这一章中(下面关于标准库的几章中同样)我的主要目的不是对每一个函数给出完整地说明，而是告诉你标准库能够提供什么功能。为了能够清楚地说明问题，我可能会忽略一些小的选项或者行为。主要的思想是激发你的好奇心，这些好奇之处可能在参考手册中找到答案。<br />
<span id="more-293"></span><br />
数学库由算术函数的标准集合组成，比如三角函数库(sin, cos, tan, asin, acos, etc.),幂指函数(exp, log, log10), 舍入函数(floor, ceil), max, min,加上一个变量 pi.数学库也定义了一个幂操作符 ′^′ 。<br />
所有的三角函数都在弧度单位下工作。(<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">Lua</a>4.0以前在度数下工作。)你可以使用deg和rad函数在度和弧度之间转换。如果你想在degree情况下使用三角函数，你可以重定义三角函数：<br />
local sin, asin, &#8230; = math.sin, math.asin, &#8230;<br />
local deg, rad = math.deg, math.rad<br />
math.sin = function (x) return sin(rad(x)) end<br />
math.asin = function (x) return deg(asin(x)) end<br />
&#8230;<br />
math.random用来产生伪随机数，有三种调用方式：<br />
第一：不带参数，将产生 [0,1)范围内的随机数.<br />
第二：带一个参数n，将产生1 &lt;= x &lt;= n范围内的随机数x.<br />
第三：带两个参数a和b,将产生a &lt;= x &lt;= b范围内的随机数x.<br />
你可以使用randomseed 设置随机数发生器的种子，只能接受一个数字参数。通常在程序开始时，使用固定的种子初始化随机数发生器，意味着每次运行程序，将产生相同的随机数序列。为了调试方便，这很有好处，但是在游戏中就意味着每次运行都拥有相同的关卡。解决这个问题的一个通常的技巧是使用当前系统时间作为种子：<br />
math.randomseed(os.time())<br />
(os.time函数返回一个表示当前系统时间的数字，通常是自新纪元以来的一个整数。)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/293/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》17.Weak 表</title>
		<link>http://www.lifangjin.com/archives/292</link>
		<comments>http://www.lifangjin.com/archives/292#comments</comments>
		<pubDate>Mon, 05 Mar 2007 14:02:29 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=292</guid>
		<description><![CDATA[Lua自动进行内存的管理。 程序只能创建对象（表，函数等），而没有执行删除对象的函数。通过使用垃圾收集技术， Lua会自动删除那些失效的对象。 这可以使你从内存管理的负担中解脱出来。更重要的，可以让你从那些由此引发的大部分BUG中解脱出来，比如指针挂起(dangling pointers)和内存溢出。 和其他的不同，Lua的垃圾收集器不存在循环的问题。 在使用循环性的数据结构的时候，你无须加入特殊的操作；他们会像其他数据一样被收集。 当然，有些时候即使更智能化的收集器也需要你的帮助。 没有任何的垃圾收集器可以让你忽略掉内存管理的所有问题。 垃圾收集器只能在确认对象失效之后才会进行收集；它是不会知道你对垃圾的定义的。 一个典型的例子就是堆栈:有一个数组和指向栈顶的索引构成。 你知道这个数组中有效的只是在顶端的那一部分，但Lua不那么认为。 如果你通过简单的出栈操作提取一个数组元素，那么数组对象的其他部分对Lua来说仍然是有效的。 同样的，任何在全局变量中声明的对象，都不是Lua认为的垃圾，即使你的程序中根本没有用到他们。 这两种情况下，你应当自己处理它（你的程序），为这种对象赋nil值，防止他们锁住其他的空闲对象。 然而，简单的清理你的声明并不总是足够的。 有些语句需要你和收集器进行额外的合作。 一个典型的例子发生在当你想在你的程序中对活动的对象（比如文件）进行收集的时候。 那看起来是个简单的任务： 你需要做的是在收集器中插入每一个新的对象。 然而，一旦对象被插入了收集器，它就不会再被收集！ 即使没有其他的指针指向它，收集器也不会做什么的。 Lua会认为这个引用是为了阻止对象被回收的，除非你告诉Lua怎么做。 Weak 表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。 一个weak引用是指一个不被Lua认为是垃圾的对象的引用。 如果一个对象所有的引用指向都是weak，对象将被收集，而那些weak引用将会被删除。 Lua通过weak tables来实现weak引用： 一个weak tables是指所有引用都是weak的table。 这意味着，如果一个对象只存在于weak tables中，Lua将会最终将它收集。 表有keys和values，而这两者都可能包含任何类型的对象。 在一般情况下，垃圾收集器并不会收集作为keys和vaules属性的对象。 也就是说，keys和vaules都属于强引用，他们可以防止他们指向的对象被回收。 在一个weak tables中，keys和vaules也可能是weak的。 那意味着这里存在三种类型的weak tables： weak keys组成的tables；weak vaules组成的tables；以及纯weak tables类型，他们的keys和vaules都是weak的。 与table本身的类型无关，当一个keys或者vaule被收集时，整个的入口(entry )都将从这个table中消失。 表的weak性由他的metatable的__mode域来指定的。在这个域存在的时候，必须是个字符串： 如果这个字符串包含小写字母‘k’，这个table中的keys就是weak的；如果这个字符串包含小写字母‘v’，这个table中的vaules就是weak的。 下面是一个例子，虽然是人造的，但是可以阐明weak tables的基本应用： a = {} b = [...]]]></description>
			<content:encoded><![CDATA[<p>Lua自动进行内存的管理。 程序只能创建对象（表，函数等），而没有执行删除对象的函数。通过使用垃圾收集技术， Lua会自动删除那些失效的对象。 这可以使你从内存管理的负担中解脱出来。更重要的，可以让你从那些由此引发的大部分BUG中解脱出来，比如指针挂起(dangling pointers)和内存溢出。<br />
和其他的不同，Lua的垃圾收集器不存在循环的问题。 在使用循环性的数据结构的时候，你无须加入特殊的操作；他们会像其他数据一样被收集。 当然，有些时候即使更智能化的收集器也需要你的帮助。 没有任何的垃圾收集器可以让你忽略掉内存管理的所有问题。<br />
<span id="more-292"></span><br />
垃圾收集器只能在确认对象失效之后才会进行收集；它是不会知道你对垃圾的定义的。 一个典型的例子就是堆栈:有一个数组和指向栈顶的索引构成。 你知道这个数组中有效的只是在顶端的那一部分，但Lua不那么认为。 如果你通过简单的出栈操作提取一个数组元素，那么数组对象的其他部分对Lua来说仍然是有效的。 同样的，任何在全局变量中声明的对象，都不是Lua认为的垃圾，即使你的程序中根本没有用到他们。 这两种情况下，你应当自己处理它（你的程序），为这种对象赋nil值，防止他们锁住其他的空闲对象。<br />
然而，简单的清理你的声明并不总是足够的。 有些语句需要你和收集器进行额外的合作。 一个典型的例子发生在当你想在你的程序中对活动的对象（比如文件）进行收集的时候。 那看起来是个简单的任务： 你需要做的是在收集器中插入每一个新的对象。 然而，一旦对象被插入了收集器，它就不会再被收集！ 即使没有其他的指针指向它，收集器也不会做什么的。 Lua会认为这个引用是为了阻止对象被回收的，除非你告诉Lua怎么做。<br />
Weak 表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。 一个weak引用是指一个不被Lua认为是垃圾的对象的引用。 如果一个对象所有的引用指向都是weak，对象将被收集，而那些weak引用将会被删除。 Lua通过weak tables来实现weak引用： 一个weak tables是指所有引用都是weak的table。 这意味着，如果一个对象只存在于weak tables中，Lua将会最终将它收集。<br />
表有keys和values，而这两者都可能包含任何类型的对象。 在一般情况下，垃圾收集器并不会收集作为keys和vaules属性的对象。 也就是说，keys和vaules都属于强引用，他们可以防止他们指向的对象被回收。 在一个weak tables中，keys和vaules也可能是weak的。 那意味着这里存在三种类型的weak tables： weak keys组成的tables；weak vaules组成的tables；以及纯weak tables类型，他们的keys和vaules都是weak的。 与table本身的类型无关，当一个keys或者vaule被收集时，整个的入口(entry )都将从这个table中消失。<br />
表的weak性由他的metatable的__mode域来指定的。在这个域存在的时候，必须是个字符串： 如果这个字符串包含小写字母‘k’，这个table中的keys就是weak的；如果这个字符串包含小写字母‘v’，这个table中的vaules就是weak的。 下面是一个例子，虽然是人造的，但是可以阐明weak tables的基本应用：<br />
a = {}</p>
<p>b = {}</p>
<p>setmetatable(a, b)</p>
<p>b.__mode = \&#8221;k\&#8221; &#8212; now `a&#8217; has weak keys</p>
<p>key = {} &#8212; creates first key</p>
<p>a[key] = 1</p>
<p>key = {} &#8212; creates second key</p>
<p>a[key] = 2</p>
<p>collectgarbage() &#8212; forces a garbage collection cycle</p>
<p>for k, v in pairs(a) do print(v) end</p>
<p>&#8211;&gt; 2</p>
<p>在这个例子中，第二个赋值语句key={}覆盖了第一个key的值。 当垃圾收集器工作时，在其他地方没有指向第一个key的引用，所以它被收集了，因此相对应的table中的入口也同时被移除了。 可是，第二个key，仍然是占用活动的变量key，所以它不会被收集。<br />
要注意，只有对象才可以从一个weak table中被收集。 比如数字和布尔值类型的值，都是不会被收集的。 例如，如果我们在table中插入了一个数值型的key（在前面那个例子中），它将永远不会被收集器从table中移除。 当然，如果对应于这个数值型key的vaule被收集，那么它的整个入口将会从weak table中被移除。<br />
关于字符串的一些细微差别： 从上面的实现来看，尽管字符串是可以被收集的，他们仍然跟其他可收集对象有所区别。 其他对象，比如tables和函数，他们都是显示的被创建。 比如，不管什么时候当Lua遇到{}时，它建立了一个新的table。 任何时候这个 function（）。。。end建立了一个新的函数（实际上是一个闭包）。 然而，<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">Lua</a> 见到“a”..”b”的时候会创建一个新的字符串？ 如果系统中已经有一个字符串”ab”的话怎么办？ Lua会重新建立一个新的？ 编译器可以在程序运行之前创建字符串么？ 这无关紧要： 这些是实现的细节。 因此，从程序员的角度来看，字符串是值而不是对象。 所以，就像数值或布尔值，一个字符串不会从weak tables中被移除（除非它所关联的vaule被收集）。<br />
17.1记忆函数<br />
一个相当普遍的编程技术是用空间来换取时间。 你可以通过记忆函数结果来进行优化，当你用同样的参数再次调用函数时，它可以自动返回记忆的结果。<br />
想像一下一个通用的服务器，接收包含Lua代码的字符串请求。 每当它收到一个请求，它调用loadstring加载字符串，然后调用函数进行处理。 然而，loadstring是一个“巨大”的函数，一些命令在服务器中会频繁地使用。 不需要反复调用loadstring和后面接着的closeconnection（），服务器可以通过使用一个辅助table来记忆loadstring的结果。 在调用loadstring之前，服务器会在这个table中寻找这个字符串是否已经有了翻译好的结果。 如果没有找到，那么（而且只是这个情况）服务器会调用loadstring并把这次的结果存入辅助table。 我们可以将这个操作包装为一个函数：<br />
local results = {}</p>
<p>function mem_loadstring (s)</p>
<p>if results[s] then &#8212; result available?</p>
<p>return results[s] &#8212; reuse it</p>
<p>else</p>
<p>local res = loadstring(s) &#8212; compute new result</p>
<p>results[s] = res &#8212; save for later reuse</p>
<p>return res</p>
<p>end</p>
<p>end</p>
<p>这个方案的存储消耗可能是巨大的。 尽管如此，它仍然可能会导致意料之外的数据冗余。 尽管一些命令一遍遍的重复执行，但有些命令可能只运行一次。 渐渐地，这个table积累了服务器所有命令被调用处理后的结果；早晚有一天，它会挤爆服务器的内存。 一个weak table提供了对于这个问题的简单解决方案。 如果这个结果表中有weak值，每次的垃圾收集循环都会移除当前时间内所有未被使用的结果（通常是差不多全部）：<br />
local results = {}</p>
<p>setmetatable(results, {__mode = \&#8221;v\&#8221;}) &#8212; make values weak</p>
<p>function mem_loadstring (s)</p>
<p>&#8230; &#8212; as before</p>
<p>setmetatable(results, {__mode = \&#8221;kv\&#8221;})</p>
<p>事实上，因为table的索引下标经常是字符串式的，如果愿意，我们可以将table全部置weak：<br />
setmetatable(results, {__mode = \&#8221;kv\&#8221;})</p>
<p>最终结果是完全一样的。<br />
记忆技术在保持一些类型对象的唯一性上同样有用。 例如，假如一个系统将通过tables表达颜色，通过有一定组合方式的红色，绿色，蓝色。 一个自然颜色调色器通过每一次新的请求产生新的颜色：<br />
function createRGB (r, g, b)</p>
<p>return {red = r, green = g, blue = b}</p>
<p>end</p>
<p>使用记忆技术，我们可以将同样的颜色结果存储在同一个table中。 为了建立每一种颜色唯一的key，我们简单的使用一个分隔符连接颜色索引下标：<br />
local results = {}</p>
<p>setmetatable(results, {__mode = \&#8221;v\&#8221;}) &#8212; make values weak</p>
<p>function createRGB (r, g, b)</p>
<p>local key = r .. \&#8221;-\&#8221; .. g .. \&#8221;-\&#8221; .. b</p>
<p>if results[key] then return results[key]</p>
<p>else</p>
<p>local newcolor = {red = r, green = g, blue = b}</p>
<p>results[key] = newcolor</p>
<p>return newcolor</p>
<p>end</p>
<p>end</p>
<p>一个有趣的后果就是，用户可以使用这个原始的等号运算符比对操作来辨别颜色，因为两个同时存在的颜色通过同一个的table来表达。 要注意，同样的颜色可能在不同的时间通过不同的tales来表达，因为垃圾收集器一次次的在清理结果table。 然而，只要给定的颜色正在被使用，它就不会从结果中被移除。 所以，任何时候一个颜色在同其他颜色进行比较的时候存活的够久，它的结果镜像也同样存活。<br />
17.2关联对象属性<br />
weak tables的另一个重要的应用就是和对象的属性关联。 在一个对象上加入更多的属性是无时无刻都会发生的： 函数名称，tables的缺省值，数组的大小，等等。<br />
当对象是表的时候，我们可以使用一个合适的唯一key来将属性保存在表中。 就像我们在前面说的那样，一个很简单并且可以防止错误的方法是建立一个新的对象（典型的比如table）然后把它当成key使用。 然而，如果对象不是table，它就不能自己保存自身的属性。 即使是tables，有些时候我们可能也不想把属性保存在原来的对象中去。 例如，我们可能希望将属性作为私有的，或者我们不想在访问table中元素的时候受到这个额外的属性的干扰。 在上述这些情况下，我们需要一个替代的方法来将属性和对象联系起来。 当然，一个外部的table提供了一种理想化的方式来联系属性和对象（tables有时被称作联合数组并不偶然）。 我们把这个对象当作key来使用，他们的属性作为vaule。 一个外部的table可以保存任何类型对象的属性（就像Lua允许我们将任何对象看作key）。 此外，保存在一个外部table的属性不会妨碍到其他的对象，并且可以像这个table本身一样私有化。<br />
然而，这个看起来完美的解决方案有一个巨大的缺点： 一旦我们在一个table中将一个对象使用为key，我们就将这个对象锁定为永久存在。 Lua不能收集一个正在被当作key使用的对象。 如果我们使用一个普通的table来关联函数和名字，那么所有的这些函数将永远不会被收集。 正如你所想的那样，我们可以通过使用weak table来解决这个问题。 这一次，我们需要weak keys。 一旦没有其他地方的引用，weak keys并不会阻止任何的key被收集。 从另一方面说，这个table不会存在weak vaules；否则，活动对象的属性就可能被收集了。<br />
Lua本身使用这种技术来保存数组的大小。 像我们下面即将看到的那样，table库提供了一个函数来设定数组的大小，另一个函数来读取数组的大小。 当你设定了一个数组的大小，Lua 将这个尺寸保存在一个私有的weak table，索引就是数组本身，而value就是它的尺寸。<br />
17.3重述带有默认值的表<br />
在章节13.4.3，我们讨论了怎样使用非nil的默认值来实现表。 我们提到一种特殊的技术并注释说另外两种技术需要使用weak tables，所以我们推迟在这里介绍他们。 现在，介绍她们的时候了。 就像我们说的那样，这两种默认值的技术实际上来源于我们前面提到的两种通用的技术的特殊应用： 对象属性和记忆。<br />
在第一种解决方案中，我们使用weak table来将默认vaules和每一个table相联系：<br />
local defaults = {}</p>
<p>setmetatable(defaults, {__mode = \&#8221;k\&#8221;})</p>
<p>local mt = {__index = function (t) return defaults[t] end}</p>
<p>function setDefault (t, d)</p>
<p>defaults[t] = d</p>
<p>setmetatable(t, mt)</p>
<p>end</p>
<p>如果默认值没有weak的keys，它就会将所有的带有默认值的tables设定为永久存在。 在第二种方法中，我们使用不同的metatables来保存不同的默认值，但当我们重复使用一个默认值的时候，重用同一个相同的metatable。 这是一个典型的记忆技术的应用：<br />
local metas = {}</p>
<p>setmetatable(metas, {__mode = \&#8221;v\&#8221;})</p>
<p>function setDefault (t, d)</p>
<p>local mt = metas[d]</p>
<p>if mt == nil then</p>
<p>mt = {__index = function () return d end}</p>
<p>metas[d] = mt &#8212; memoize</p>
<p>end</p>
<p>setmetatable(t, mt)</p>
<p>end</p>
<p>这种情况下，我们使用weak vaules，允许将不会被使用的metatables 可以被回收。<br />
把这两种方法放在一起，哪个更好？ 通常，取决于具体情况。 它们都有相似的复杂性和相似的性能。 第一种方法需要在每个默认值的tables中添加一些文字（一个默认的入口）。 第二种方法需要在每个不同的默认值加入一些文字（一个新的表，一个新的闭包，metas中新增入口）。 所以，如果你的程序有数千个tables，而这些表只有很少数带有不同默认值的，第二种方法显然更优秀。 另一方面，如果只有很少的tabels可以共享相同的默认vaules，那么你还是用第一种方法吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/292/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》16.面向对象的程序设计</title>
		<link>http://www.lifangjin.com/archives/291</link>
		<comments>http://www.lifangjin.com/archives/291#comments</comments>
		<pubDate>Mon, 05 Mar 2007 14:01:24 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=291</guid>
		<description><![CDATA[Lua中的表不仅在某种意义上是一种对象。像对象一样，表也有状态(成员变量)；也有与对象的值独立的本性，特别是拥有两个不同值的对象(table)代表两个不同的对象；一个对象在不同的时候也可以有不同的值，但他始终是一个对象；表也有芷冢胨唇ㄋ裁词焙虼唇ㄋ薰亍? 对象有他们的成员函数，表也有： Account = {balance = 0} function Account.withdraw (v) Account.balance = Account.balance &#8211; v end 这个定义创建了一个新的函数，并且保存在Account对象的withdraw域内，下面我们可以这样调用： Account.withdraw(100.00) 这种函数就是我们所谓的方法，然而，在一个函数内部使用全局变量名Account是一个不好的习惯。首先，这个函数只能在这个特殊的对象(译者：指Account)中使用；第二，即使对这个特殊的对象而言，这个函数也只有在对象被存储在特殊的变量(译者：指Account)中才可以使用。如果我们改变了这个对象的名字，函数withdraw将不能工作： a = Account; Account = nil a.withdraw(100.00) &#8212; ERROR! 这种行为违背了前面的对象应该有独立的生命周期的原则。 一个灵活的方法是：定义方法的时候带上一个额外的参数，来表示方法作用的对象。这个参数经常为self或者this： function Account.withdraw (self, v) self.balance = self.balance &#8211; v end 现在，当我们调用这个方法的时候不需要指定他操作的对象了： a1 = Account; Account = nil &#8230; a1.withdraw(a1, 100.00) &#8212; OK 使用self参数定义函数后，我们可以将这个函数用于多个对象上： a2 = [...]]]></description>
			<content:encoded><![CDATA[<p>Lua中的表不仅在某种意义上是一种对象。像对象一样，表也有状态(成员变量)；也有与对象的值独立的本性，特别是拥有两个不同值的对象(table)代表两个不同的对象；一个对象在不同的时候也可以有不同的值，但他始终是一个对象；表也有芷冢胨唇ㄋ裁词焙虼唇ㄋ薰亍?<br />
对象有他们的成员函数，表也有：<br />
Account = {balance = 0}</p>
<p>function Account.withdraw (v)</p>
<p>Account.balance = Account.balance &#8211; v</p>
<p>end</p>
<p>这个定义创建了一个新的函数，并且保存在Account对象的withdraw域内，下面我们可以这样调用：<br />
Account.withdraw(100.00)</p>
<p><span id="more-291"></span><br />
这种函数就是我们所谓的方法，然而，在一个函数内部使用全局变量名Account是一个不好的习惯。首先，这个函数只能在这个特殊的对象(译者：指Account)中使用；第二，即使对这个特殊的对象而言，这个函数也只有在对象被存储在特殊的变量(译者：指Account)中才可以使用。如果我们改变了这个对象的名字，函数withdraw将不能工作：<br />
a = Account; Account = nil</p>
<p>a.withdraw(100.00) &#8212; ERROR!</p>
<p>这种行为违背了前面的对象应该有独立的生命周期的原则。<br />
一个灵活的方法是：定义方法的时候带上一个额外的参数，来表示方法作用的对象。这个参数经常为self或者this：<br />
function Account.withdraw (self, v)</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>现在，当我们调用这个方法的时候不需要指定他操作的对象了：<br />
a1 = Account; Account = nil<br />
&#8230;<br />
a1.withdraw(a1, 100.00) &#8212; OK</p>
<p>使用self参数定义函数后，我们可以将这个函数用于多个对象上：<br />
a2 = {balance=0, withdraw = Account.withdraw}</p>
<p>&#8230;</p>
<p>a2.withdraw(a2, 260.00)</p>
<p>self参数的使用是很多面向对象语言的要点。大多数OO语言将这种机制隐藏起来，这样程序员不必声明这个参数(虽然仍然可以在方法内使用这个参数)。Lua也提供了通过使用冒号操作符来隐藏这个参数的声明。我们可以重写上面的代码：<br />
function Account:withdraw (v)</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>调用方法如下：<br />
a:withdraw(100.00)</p>
<p>冒号的效果相当于在函数定义和函数调用的时候，增加一个额外的隐藏参数。这种方式只是提供了一种方便的语法，实际上并没有什么新的内容。我们可以使用dot语法定义函数而用冒号语法调用函数，反之亦然，只要我们正确的处理好额外的参数：<br />
Account = { balance=0,</p>
<p>withdraw = function (self, v)</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>}</p>
<p>function Account:deposit (v)</p>
<p>self.balance = self.balance + v</p>
<p>end</p>
<p>Account.deposit(Account, 200.00)</p>
<p>Account:withdraw(100.00)</p>
<p>现在我们的对象拥有一个标示符，一个状态和操作这个状态的方法。但他们依然缺少一个class系统，继承和隐藏。先解决第一个问题：我们如何才能创建拥有相似行为的多个对象呢？明确地说，我们怎样才能创建多个accounts?(译者：针对上面的对象Account而言)<br />
16.1类<br />
一些面向对象的语言中提供了类的概念，作为创建对象的模板。在这些语言里，对象是类的实例。Lua不存在类的概念，每个对象定义他自己的行为并拥有自己的形状(shape)。然而，依据基于原型(prototype)的语言比如Self和NewtonScript，在Lua中仿效类的概念并不难。在这些语言中，对象没有类。不过，每一个韵笥幸桓鲈停褪且桓銎胀ǖ亩韵螅ㄒ辶说谝桓龆韵笳也坏匠稍薄Ｔ谡庑┯镅灾形吮硎纠啵颐羌虻ゴ唇ㄒ桓龆韵笞庞美醋魑渌韵蟮脑汀２还苁抢嗷故窃投际嵌ㄒ辶艘恍┖芏喽韵蠊蚕淼男形牡胤健?<br />
在Lua中，使用前面章节我们介绍过的继承的思想，很容易实现prototypes.更明确的来说，如果我们有两个对象a和b，我们想让b作为a的prototype只需要：<br />
setmetatable(a, {__index = b})</p>
<p>这样，对象a调用任何不存在的成员都会到对象b中查找。术语上，可以将b看作类，a看作对象。回到前面银行账号的例子上。为了使得新创建的对象拥有和Account相似的行为，我们使用__index metamethod，使新的对象继承Account。注意一个小的优化：我们不需要创建一个额外的表作为account对象的metatable；我们可以用Account表本身作为metatable：<br />
function Account:new (o)</p>
<p>o = o or {} &#8212; create object if user does not provide one</p>
<p>setmetatable(o, self)</p>
<p>self.__index = self</p>
<p>return o</p>
<p>end</p>
<p>(当我们调用Account:new时，self等于Account；因此我们可以直接使用Account取代self。然而，使用self在我们下一节介绍类继承时更合适).有了这段代码之后，当我们创建一个新的账号并且掉用一个方法的时候，有什么发生呢？<br />
a = Account:new{balance = 0}</p>
<p>a:deposit(100.00)</p>
<p>当我们创建这个新的账号a的时候，a将Account作为他的metatable(调用Account:new时，self即Account)。当我们调用a:deposit(100.00)，我们实际上调用的是a.deposit(a,100.00)(冒号仅仅是语法上的便利)。然而，Lua在表a中找不到deposit，因此他回到metatable的__index对应的表中查找，情况大致如下:<br />
getmetatable(a).__index.deposit(a, 100.00)</p>
<p>a的metatable是Account ，Account.__index也是Account (因为new函数中self.__index = self). 所以我们可以重写上面的代码为：<br />
Account.deposit(a, 100.00)</p>
<p>也就是说，Lua传递a作为self参数调用原始的deposit函数。所以，新的账号对象从Account继承了deposit方法。使用同样的机制，可以从Account继承所有的域。继承机制不仅对方法有效，对表中所有的域都有效。所以，一个类不仅提供方法，也提供了他的实例的成员的默认值。记住：在我们第一个Account定义中，我们提供了成员balance默认值为0，所以，如果我们创建一个新的账号而没有提供balance的初始值，他将继承默认值：<br />
b = Account:new()</p>
<p>print(b.balance) &#8211;&gt; 0</p>
<p>当我们调用b的deposit方法时，实际等价于：<br />
b.balance = b.balance + v</p>
<p>(因为self就是b). 表达式b.balance 等于0并且初始的存款(b.balance)被赋予b.balance。下一次我们访问这个值的时候，不会在涉及到index metamethod，因为b已经存在他自己的balance域。<br />
16.2继承<br />
通常面向对象语言中，继承使得类可以访问其他类的方法，这在Lua中也很容易现实：<br />
假定我们有一个基类Account:<br />
Account = {balance = 0}</p>
<p>function Account:new (o)</p>
<p>o = o or {}</p>
<p>setmetatable(o, self)</p>
<p>self.__index = self</p>
<p>return o</p>
<p>end</p>
<p>function Account:deposit (v)</p>
<p>self.balance = self.balance + v</p>
<p>end</p>
<p>function Account:withdraw (v)</p>
<p>if v &gt; self.balance then error&#8221;insufficient funds&#8221; end</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>我们打算从基类派生出一个子类SpecialAccount，这个子类允许客户取款超过它的存款余额限制，我们从一个空类开始，从基类继承所有操作：<br />
SpecialAccount = Account:new()</p>
<p>到现在为止，SpecialAccount仅仅是Account的一个实例。现在奇妙的事情发生了：<br />
s = SpecialAccount:new{limit=1000.00}</p>
<p>SpecialAccount从Account继承了new方法，当new执行的时候，self参数指向SpecialAccount. 所以, s的metatable是SpecialAccount, __index 也是SpecialAccount.这样，s继承了 SpecialAccount, 后者继承了Account. 当我们执行：<br />
s:deposit(100.00)</p>
<p>Lua在s中找不到deposit域，他会到SpecialAccount中查找，在SpecialAccount中找不到，会到Account中查找。使得SpecialAccount特殊之处在于，它可以重定义从父类中继承来的方法：<br />
function SpecialAccount:withdraw (v)</p>
<p>if v &#8211; self.balance &gt;= self:getLimit() then</p>
<p>error&#8221;insufficient funds&#8221;</p>
<p>end</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>function SpecialAccount:getLimit ()</p>
<p>return self.limit or 0</p>
<p>end</p>
<p>现在，当我们调用方法s:withdraw(200.00), Lua不会到Account中查找,因为它第一次救在SpecialAccount中发现了新的withdraw方法，由于s.limit等于1000.00 (记住我们创建s的时候初始化了这个值),程序执行了取款操作，s的balance变成了负值。<br />
在Lua中面向对象有趣的一个方面是你不需要创建一个新类去指定一个新的行为。如果仅仅一个对象需要特殊的行为，你可以直接在对象中实现，例如，如果账号s表示一些特殊的客户：取款限制是他的存款的10%，你只需要修改这个单独的账号：<br />
function s:getLimit ()</p>
<p>return self.balance * 0.10</p>
<p>end</p>
<p>这样声明之后，调用s:withdraw(200.00)将运行SpecialAccount的withdraw 方法,但是当方法调用self:getLimit时,最后的定义被触发。<br />
16.3多重继承<br />
由于Lua中的对象不是元生(primitive)的，所以在Lua中有很多方法可以实现面向对象的程序设计。我们前面所见到的使用index metamethod的方法可能是简洁、性能、灵活各方面综合最好的。然而，针对一些特殊情况也有更适合的实现方式。下面我们在Lua中多重继承的实现。<br />
实现的关键在于：将函数用作__index。记住，当一个表的metatable存在一个__index函数时，如果Lua调用一个原始表中不存在的函数，Lua将调用这个__index指定的函数。这样可以用__index实现在多个父类中查找子类不存在的域。<br />
多重继承意味着一个类拥有多个父类，所以，我们不能用创建一个类的方法去创建子类。取而代之的是，我们定义一个特殊的函数createClass来完成这个功能，将被创建的新类的父类作为这个函数的参数。这个函数创建一个表来表示新类，并且将它的metatable设定为一个可以实现多继承的__index metamethod。尽管是多重继承，每一个实例依然属于一个在其中能找得到它需要的方法的单独的类。所以，这种类和父类之间的关系与传统的类与实例的关系是有区别的。特别是，一个类不能同时是其实例的metatable又是自己的metatable。在下面的实现中，我们将一个类作为他的实例的metatable，创建另一个表作为类的metatable:</p>
<p>&#8211; look up for `k&#8217; in list of tables `plist&#8217;</p>
<p>local function search (k, plist)</p>
<p>for i=1, table.getn(plist) do</p>
<p>local v = plist[i][k] &#8212; try `i&#8217;-th superclass</p>
<p>if v then return v end</p>
<p>end</p>
<p>end</p>
<p>function createClass (&#8230;)</p>
<p>local c = {} &#8212; new class</p>
<p>&#8211; class will search for each method in the list of its</p>
<p>&#8211; parents (`arg&#8217; is the list of parents)</p>
<p>setmetatable(c, {__index = function (t, k)</p>
<p>return search(k, arg)</p>
<p>end})</p>
<p>&#8211; prepare `c&#8217; to be the metatable of its instances</p>
<p>c.__index = c</p>
<p>&#8211; define a new constructor for this new class</p>
<p>function c:new (o)</p>
<p>o = o or {}</p>
<p>setmetatable(o, c)</p>
<p>return o</p>
<p>end</p>
<p>&#8211; return new class</p>
<p>return c</p>
<p>end</p>
<p>让我们用一个小例子阐明一下createClass的使用，假定我们前面的类Account和另一个类Named，Named只有两个方法setname and getname:<br />
Named = {}</p>
<p>function Named:getname ()</p>
<p>return self.name</p>
<p>end</p>
<p>function Named:setname (n)</p>
<p>self.name = n</p>
<p>end</p>
<p>为了创建一个继承于这两个类的新类，我们调用createClass：<br />
NamedAccount = createClass(Account, Named)</p>
<p>为了创建和使用实例，我们像通常一样：<br />
account = NamedAccount:new{name = &#8220;Paul&#8221;}</p>
<p>print(account:getname()) &#8211;&gt; Paul</p>
<p>现在我们看看上面最后一句发生了什么，Lua在account中找不到getname，因此他查找account的metatable的__index，即NamedAccount。但是，NamedAccount也没有getname，因此Lua查找NamedAccount 的metatable的__index，因为这个域包含一个函数，Lua调用这个函数并首先到Account中查找getname，没有找到，然后到Named中查找，找到并返回最终的结果。当然，由于搜索的复杂性，多重继承的效率比起单继承要低。一个简单的改善性能的方法是将继承方法拷贝到子类。使用这种技术，index方法如下：<br />
&#8230;</p>
<p>setmetatable(c, {__index = function (t, k)</p>
<p>local v = search(k, arg)</p>
<p>t[k] = v &#8212; save for next access</p>
<p>return v</p>
<p>end})</p>
<p>&#8230;</p>
<p>应用这个技巧，访问继承的方法和访问局部方法一样快(特别是第一次访问)。缺点是系统运行之后，很难改变方法的定义，因为这种改变不能影响继承链的下端。<br />
16.4私有性(privacy)<br />
很多人认为私有性是面向对象语言的应有的一部分。每个对象的状态应该是这个对象自己的事情。在一些面向对象的语言中，比如C++和Java你可以控制对象成员变量或者成员方法是否私有。其他一些语言比如Smalltalk中，所有的成员变量都是私有，所有的成员方法都是公有的。第一个面向对象语言Simula不提供任何保护成员机制。<br />
如前面我们所看到的Lua中的主要对象设计不提供私有性访问机制。部分原因因为这是我们使用通用数据结构tables来表示对象的结果。但是这也反映了后来的Lua的设计思想。Lua没有打算被用来进行大型的程序设计，相反，Lua目标定于小型到中型的程序设计，通常是作为大型系统的一部分。典型的，被一个或者很少几个程序员开发，甚至被非程序员使用。所以，Lua避免太冗余和太多的人为限制。如果你不想访问一个对象内的一些东西就不要访问(If you do not want to access something inside an object, just do not do it.)。<br />
然而，Lua的另一个目标是灵活性，提供程序员元机制(meta-mechanisms),通过他你可以实现很多不同的机制。虽然Lua中基本的面向对象设计并不提供私有性访问的机制，我们可以用不同的方式来实现他。虽然这种实现并不常用，但知道他也是有益的，不仅因为它展示了Lua的一些有趣的角落，也因为它可能是某些问题的很好地解决方案。设计的基本思想是，每个对象用两个表来表示：一个描述状态；另一个描述操作(或者叫接口)。对象本身通过第二个表来访问，也就是说，通过接口来访问对象。为了避免未授权的访问，表示状态的表中不涉及到操作；表示操作的表也不涉及到状态，取而代之的是，状态被保存在方法的闭包内。例如，用这种设计表述我们的银行账号，我们使用下面的函数工厂创建新的对象：<br />
function newAccount (initialBalance)</p>
<p>local self = {balance = initialBalance}</p>
<p>local withdraw = function (v)</p>
<p>self.balance = self.balance &#8211; v</p>
<p>end</p>
<p>local deposit = function (v)</p>
<p>self.balance = self.balance + v</p>
<p>end</p>
<p>local getBalance = function () return self.balance end</p>
<p>return {</p>
<p>withdraw = withdraw,</p>
<p>deposit = deposit,</p>
<p>getBalance = getBalance</p>
<p>}</p>
<p>end</p>
<p>首先，函数创建一个表用来描述对象的内部状态，并保存在局部变量self内。然后，函数为对象的每一个方法创建闭包(也就是说，嵌套的函数实例)。最后，函数创建并返回外部对象，外部对象中将局部方法名指向最终要实现的方法。这儿的关键点在于：这些方法没有使用额外的参数self，代替的是直接访问self。因为没有这个额外的参数，我们不能使用冒号语法来访问这些对象。函数只能像其他函数一样调用：<br />
acc1 = newAccount(100.00)</p>
<p>acc1.withdraw(40.00)</p>
<p>print(acc1.getBalance()) &#8211;&gt; 60</p>
<p>这种设计实现了任何存储在self表中的部分都是私有的，newAccount返回之后，没有什么方法可以直接访问对象，我们只能通过newAccount中定义的函数来访问他。虽然我们的例子中仅仅将一个变量放到私有表中，但是我们可以将对象的任何的部分放到私有表中。我们也可以定义私有方法，他们看起来象公有的，但我们并不将其放到接口中。例如，我们的账号可以给某些用户取款享有额外的10%的存款上限，但是我们不想用户直接访问这种计算的详细信息，我们实现如下：<br />
function newAccount (initialBalance)</p>
<p>local self = {</p>
<p>balance = initialBalance,</p>
<p>LIM = 10000.00,</p>
<p>}</p>
<p>local extra = function ()</p>
<p>if self.balance &gt; self.LIM then</p>
<p>return self.balance*0.10</p>
<p>else</p>
<p>return 0</p>
<p>end</p>
<p>end</p>
<p>local getBalance = function ()</p>
<p>return self.balance + self.extra()</p>
<p>end</p>
<p>&#8230;</p>
<p>这样，对于用户而言就没有办法直接访问extra函数了。<br />
16.5Single-Method的对象实现方法<br />
前面的OO程序设计的方法有一种特殊情况：对象只有一个单一的方法。这种情况下，我们不需要创建一个接口表，取而代之的是，我们将这个单一的方法作为对象返回。这听起来有些不可思议，如果需要可以复习一下Section 7.1，那里我们介绍了如何构造迭代子函数来保存闭包的状态。其实，一个保存状态的迭代子函数就是一个single-method对象。<br />
关于single-method的对象一个有趣的情况是：当这个single-method实际是一个基于重要的参数而执行不同的任务的分派(dispatch)方法时。针对这种对象：<br />
function newObject (value)</p>
<p>return function (action, v)</p>
<p>if action == &#8220;get&#8221; then return value</p>
<p>elseif action == &#8220;set&#8221; then value = v</p>
<p>else error(&#8220;invalid action&#8221;)</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>使用起来很简单：<br />
d = newObject(0)</p>
<p>print(d(&#8220;get&#8221;)) &#8211;&gt; 0</p>
<p>d(&#8220;set&#8221;, 10)</p>
<p>print(d(&#8220;get&#8221;)) &#8211;&gt; 10</p>
<p>这种非传统的对象实现是非常有效的，语法 d(&#8220;set&#8221;,10)虽然很罕见，但也只不过比传统的d:set(10)长两个字符而已。每一个对象是用一个单独的闭包，代价比起表来小的多。这种方式没有继承但有私有性：访问对象状态的唯一方式是通过它的内部方法。<br />
Tcl/Tk 的窗口部件(widgets)使用了相似的方法，在Tk中一个窗口部件的名字表示一个在窗口部件上执行各种可能操作的函数(a widget command)。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/291/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》15.Packages</title>
		<link>http://www.lifangjin.com/archives/272</link>
		<comments>http://www.lifangjin.com/archives/272#comments</comments>
		<pubDate>Thu, 22 Feb 2007 15:55:12 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=272</guid>
		<description><![CDATA[很多语言专门提供了某种机制组织全局变量的命名，比如Modula的modules，Java和Perl的packages，C++的namespaces。每一种机制对在package中声明的元素的可见性以及其他一些细节的使用都有不同的规则。但是他们都提供了一种避免不同库中命名冲突的问题的机制。每一个程序库创建自己的命名空间，在这个命名空间中定义的名字和其他命名空间中定义的名字互不干涉。 Lua并没有提供明确的机制来实现packages。然而，我们通过语言提供的基本的机制很容易实现他。主要的思想是：像标准库一样，使用表来描述package。 使用表实现packages的明显的好处是：我们可以像其他表一样使用packages，并且可以使用语言提供的所有的功能，带来很多便利。大多数语言中，packages不是第一类值(first-class values)（也就是说，他们不能存储在变量里，不能作为函数参数。。。）因此，这些语言需要特殊的方法和技巧才能实现类似的功能。 Lua中，虽然我们一直都用表来实现pachages，但也有其他不同的方法可以实现package，在这一章，我们将介绍这些方法。 基本方法 第一包的简单的方法是对包内的每一个对象前都加包名作为前缀。例如，假定我们正在写一个操作复数的库：我们使用表来表示复数，表有两个域r(实数部分)和i(虚数部分)。我们在另一张表中声明我们所有的操作来实现一个包: complex = {} function complex.new (r, i) return {r=r, i=i} end &#8211; defines a constant `i&#8217; 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 &#8211; c2.r, c1.i &#8211; c2.i) end function complex.mul (c1, c2) [...]]]></description>
			<content:encoded><![CDATA[<p>很多语言专门提供了某种机制组织全局变量的命名，比如Modula的modules，Java和Perl的packages，C++的namespaces。每一种机制对在package中声明的元素的可见性以及其他一些细节的使用都有不同的规则。但是他们都提供了一种避免不同库中命名冲突的问题的机制。每一个程序库创建自己的命名空间，在这个命名空间中定义的名字和其他命名空间中定义的名字互不干涉。<br />
Lua并没有提供明确的机制来实现packages。然而，我们通过语言提供的基本的机制很容易实现他。主要的思想是：像标准库一样，使用表来描述package。<br />
使用表实现packages的明显的好处是：我们可以像其他表一样使用packages，并且可以使用语言提供的所有的功能，带来很多便利。大多数语言中，packages不是第一类值(first-class values)（也就是说，他们不能存储在变量里，不能作为函数参数。。。）因此，这些语言需要特殊的方法和技巧才能实现类似的功能。<br />
Lua中，虽然我们一直都用表来实现pachages，但也有其他不同的方法可以实现package，在这一章，我们将介绍这些方法。<br />
<span id="more-272"></span><br />
基本方法<br />
第一包的简单的方法是对包内的每一个对象前都加包名作为前缀。例如，假定我们正在写一个操作复数的库：我们使用表来表示复数，表有两个域r(实数部分)和i(虚数部分)。我们在另一张表中声明我们所有的操作来实现一个包:<br />
complex = {}</p>
<p>function complex.new (r, i) return {r=r, i=i} end</p>
<p>&#8211; defines a constant `i&#8217;</p>
<p>complex.i = complex.new(0, 1)</p>
<p>function complex.add (c1, c2)</p>
<p>return complex.new(c1.r + c2.r, c1.i + c2.i)</p>
<p>end</p>
<p>function complex.sub (c1, c2)</p>
<p>return complex.new(c1.r &#8211; c2.r, c1.i &#8211; c2.i)</p>
<p>end</p>
<p>function complex.mul (c1, c2)</p>
<p>return complex.new(c1.r*c2.r &#8211; c1.i*c2.i,</p>
<p>c1.r*c2.i + c1.i*c2.r)</p>
<p>end</p>
<p>function complex.inv ©</p>
<p>local n = c.r^2 + c.i^2</p>
<p>return complex.new(c.r/n, -c.i/n)</p>
<p>end</p>
<p>return complex</p>
<p>这个库定义了一个全局名：coplex。其他的定义都是放在这个表内。<br />
有了上面的定义，我们就可以使用符合规范的任何复数操作了，如：<br />
c = complex.add(complex.i, complex.new(10, 20))</p>
<p>这种使用表来实现的包和真正的包的功能并不完全相同。首先，我们对每一个函数定义都必须显示的在前面加上包的名称。第二，同一包内的函数相互调用必须在被调用函数前指定包名。我们可以使用固定的局部变量名，来改善这个问题，然后，将这个局部变量赋值给最终的包。依据这个原则，我们重写上面的代码：<br />
local P = {}</p>
<p>complex = P &#8212; package name</p>
<p>P.i = {r=0, i=1}</p>
<p>function P.new (r, i) return {r=r, i=i} end</p>
<p>function P.add (c1, c2)</p>
<p>return P.new(c1.r + c2.r, c1.i + c2.i)</p>
<p>end</p>
<p>&#8230;</p>
<p>当在同一个包内的一个函数调用另一个函数的时候(或者她调用自身)，他仍然需要加上前缀名。至少，它不再依赖于固定的包名。另外，只有一个地方需要包名。可能你注意到包中最后一个语句：<br />
return complex</p>
<p>这个return语句并非必需的，因为package已经赋值给全局变量complex了。但是，我们认为package打开的时候返回本身是一个很好的习惯。额外的返回语句并不会花费什么代价，并且提供了另一种操作package的可选方式。<br />
私有成员(Privacy)<br />
有时候，一个package公开他的所有内容，也就是说，任何package的客户端都可以访问他。然而，一个package拥有自己的私有部分(也就是只有package本身才能访问)也是很有用的。在Lua中一个传统的方法是将私有部分定义为局部变量来实现。例如，我们修改上面的例子增加私有函数来检查一个值是否为有效的复数：<br />
local P = {}</p>
<p>complex = P</p>
<p>local function checkComplex ©</p>
<p>if not ((type© == &#8220;table&#8221;) and</p>
<p>tonumber(c.r) and tonumber(c.i)) then</p>
<p>error(&#8220;bad complex number&#8221;, 3)</p>
<p>end</p>
<p>end</p>
<p>function P.add (c1, c2)</p>
<p>checkComplex(c1);</p>
<p>checkComplex(c2);</p>
<p>return P.new(c1.r + c2.r, c1.i + c2.i)</p>
<p>end</p>
<p>&#8230;</p>
<p>return P</p>
<p>这种方式各有什么优点和缺点呢？package中所有的名字都在一个独立的命名空间中。Package中的每一个实体(entity)都清楚地标记为公有还是私有。另外，我们实现一个真正的隐私(privacy):私有实体在package外部是不可访问的。缺点是访问同一个package内的其他公有的实体写法冗余，必须加上前缀P.。还有一个大的问题是，当我们修改函数的状态(公有变成私有或者私有变成公有)我们必须修改函数得调用方式。<br />
有一个有趣的方法可以立刻解决这两个问题。我们可以将package内的所有函数都声明为局部的，最后将他们放在最终的表中。按照这种方法，上面的complex package修改如下：<br />
local function checkComplex ©</p>
<p>if not ((type© == &#8220;table&#8221;)</p>
<p>and tonumber(c.r) and tonumber(c.i)) then</p>
<p>error(&#8220;bad complex number&#8221;, 3)</p>
<p>end</p>
<p>end</p>
<p>local function new (r, i) return {r=r, i=i} end</p>
<p>local function add (c1, c2)</p>
<p>checkComplex(c1);</p>
<p>checkComplex(c2);</p>
<p>return new(c1.r + c2.r, c1.i + c2.i)</p>
<p>end</p>
<p>&#8230;</p>
<p>complex = {</p>
<p>new = new,</p>
<p>add = add,</p>
<p>sub = sub,</p>
<p>mul = mul,</p>
<p>div = div,</p>
<p>}</p>
<p>现在我们不再需要调用函数的时候在前面加上前缀，公有的和私有的函数调用方法相同。在package的结尾处，有一个简单的列表列出所有公有的函数。可能大多数人觉得这个列表放在package的开始处更自然，但我们不能这样做，因为我们必须首先定义局部函数。<br />
包与文件<br />
我们经常写一个package然后将所有的代码放到一个单独的文件中。然后我们只需要执行这个文件即加载package。例如，如果我们将上面我们的复数的package代码放到一个文件complex.lua中，命令“require complex”将打开这个package。记住require命令不会将相同的package加载多次。<br />
需要注意的问题是，搞清楚保存package的文件名和package名的关系 。当然，将他们联系起来是一个好的想法，因为require命令使用文件而不是packages。一种解决方法是在package的后面加上后缀(比如.<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">lua</a>)来命名文件。Lua并不需要固定的扩展名，而是由你的路径设置决定。例如，如果你的路径包含：&#8221;/usr/local/lualibs/?.<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">lua</a>&#8221;，那么复数package可能保存在一个complex.lua文件中。<br />
有些人喜欢先命名文件后命名package。也就是说，如果你重命名文件，package也会被重命名。这个解决方法提供了很大的灵活性。例如，如果你有两个有相同名称的package，你不需要修改任何一个，只需要重命名一下文件。在Lua中我们使用_REQUIREDNAME变量来重命名。记住，当require加载一个文件的时候，它定义了一个变量来表示虚拟的文件名。因此，在你的package中可以这样写：</p>
<p>local P = {} &#8212; package</p>
<p>if _REQUIREDNAME == nil then</p>
<p>complex = P</p>
<p>else</p>
<p>_G[_REQUIREDNAME] = P</p>
<p>end</p>
<p>代码中的if测试使得我们可以不需要require就可以使用package。如果_REQUIREDNAME没有定义，我们用固定的名字表示package(例子中complex)。另外，package使用虚拟文件名注册他自己。如果以使用者将库放到文件cpx.lua中并且运行require cpx，那么package将本身加载到表cpx中。如果其他的使用者将库改名为cpx_v1.lua并且运行require cpx_v1，那么package将自动将本身加载到表cpx_v1当中。<br />
使用全局表<br />
上面这些创建package的方法的缺点是：他们要求程序员注意很多东西，比如，在声明的时候也很容易忘掉local关键字。全局变量表的Metamethods提供了一些有趣的技术，也可以用来实现package。这些技术中共同之处在于：package使用独占的环境。这很容易实现：如果我们改变了package主chunk的环境，那么由package创建的所有函数都共享这个新的环境。<br />
最简单的技术实现。一旦package有一个独占的环境，不仅所有她的函数共享环境，而且它的所有全局变量也共享这个环境。所以，我们可以将所有的公有函数声明为全局变量，然后他们会自动作为独立的表(表指package的名字)存在，所有package必须要做的是将这个表注册为package的名字。下面这段代码阐述了复数库使用这种技术的结果：</p>
<p>local P = {}</p>
<p>complex = P</p>
<p>setfenv(1, P)</p>
<p>现在，当我们声明函数add,她会自动变成 complex.add:<br />
function add (c1, c2)</p>
<p>return new(c1.r + c2.r, c1.i + c2.i)</p>
<p>end</p>
<p>另外，我们可以在这个package中不需要前缀调用其他的函数。例如，add函数调用new函数，环境会自动转换为complex.new。这种方法提供了对package很好的支持：程序员几乎不需要做什么额外的工作，调用同一个package内的函数不需要前缀，调用公有和私有函数也没什么区别。如果程序员忘记了local关键字，也不会污染全局命名空间，只不过使得私有函数变成公有函数而已。另外，我们可以将这种技术和前一节我们使用的package名的方法组合起来：</p>
<p>local P = {} &#8212; package</p>
<p>if _REQUIREDNAME == nil then</p>
<p>complex = P</p>
<p>else</p>
<p>_G[_REQUIREDNAME] = P</p>
<p>end</p>
<p>setfenv(1, P)</p>
<p>这样就不能访问其他的packages了。一旦我们将一个空表P作为我们的环境，我们就失去了访问所有以前的全局变量。下面有好几种方法可以解决这个问题，但都各有利弊。<br />
最简单的解决方法是使用继承，像前面我们看到的一样：<br />
local P = {} &#8212; package</p>
<p>setmetatable(P, {__index = _G})</p>
<p>setfenv(1, P)</p>
<p>(你必须在调用setfenv 之前调用setmetatable，你能说出原因么？) 使用这种结构，package就可以直接访问所有的全局标示符，但必须为每一个访问付出一小点代价。理论上来讲，这种解决方法带来一个有趣的结果：你的package现在包含了所有的全局变量。例如，使用你的package人也可以调用标准库的sin函数： complex.math.sin(x). (Perl&#8217;s package 系统也有这种特性.)<br />
另外一种快速的访问其他packages的方法是声明一个局部变量来保存老的环境：<br />
local P = {}</p>
<p>pack = P</p>
<p>local _G = _G</p>
<p>setfenv(1, P)</p>
<p>现在，你必须对外部的访问加上前缀_G., 但是访问速度更快，因为这不涉及到metamethod 。与继承不同的是这种方法，使得你可以访问老的环境；这种方法的好与坏是有争议的，但是有时候你可能需要这种灵活性。<br />
一个更加正规的方法是：只把你需要的函数或者packages声明为local：<br />
local P = {}</p>
<p>pack = P</p>
<p>&#8211; Import Section:</p>
<p>&#8211; declare everything this package needs from outside</p>
<p>local sqrt = math.sqrt</p>
<p>local io = io</p>
<p>&#8211; no more external access after this point</p>
<p>setfenv(1, P)</p>
<p>这一技术要求稍多，但他使你的package的独立性比较好。他的速度也比前面那几种方法快。<br />
其他一些技巧(Other Facilities)<br />
正如前面我所说的，用表来实现packages过程中可以使用Lua的所有强大的功能。这里面有无限的可能性。在这里，我只给出一些建议。<br />
我们不需要将package的所有公有成员的定义放在一起，例如，我们可以在一个独立分开的chunk中给我们的复数package增加一个新的函数：<br />
function complex.div (c1, c2)</p>
<p>return complex.mul(c1, complex.inv(c2))</p>
<p>end</p>
<p>(但是注意：私有成员必须限制在一个文件之内，我认为这是一件好事。)反过来，我们可以在同一个文件之内定义多个packages，我们需要做的只是将每一个package放在一个do 代码块内，这样local变量才能被限制在那个代码块中。<br />
在package外部，如果我们需要经常使用某个函数，我们可以给他们定义一个局部变量名：<br />
local add, i = complex.add, complex.i</p>
<p>c1 = add(complex.new(10, 20), i)</p>
<p>如果我们不想一遍又一遍的重写package名，我们用一个短的局部变量表示package：<br />
local C = complex</p>
<p>c1 = C.add(C.new(10, 20), C.i)</p>
<p>写一个函数拆开package也是很容易的，将package中所有的名字放到全局命名空间即可：<br />
function openpackage (ns)</p>
<p>for n,v in pairs(ns) do _G[n] = v end</p>
<p>end</p>
<p>openpackage(complex)</p>
<p>c1 = mul(new(10, 20), i)</p>
<p>如果你担心打开package的时候会有命名冲突，可以在赋值以前检查一下名字是否存在：<br />
function openpackage (ns)</p>
<p>for n,v in pairs(ns) do</p>
<p>if _G[n] ~= nil then</p>
<p>error(&#8220;name clash: &#8221; .. n .. &#8221; is already defined&#8221;)</p>
<p>end</p>
<p>_G[n] = v</p>
<p>end</p>
<p>end</p>
<p>由于packages本身也是表，我们甚至可以在packages中嵌套packages；也就是说我们在一个package内还可以创建package，然后很少有必要这么做。<br />
另一个有趣之处是自动加载：函数只有被实际使用的时候才会自动加载。当我们加载一个自动加载的package，会自动创建一个新的空表来表示package并且设置表的__index metamethod来完成自动加载。当我们调用任何一个没有被加载的函数的时候，__index metamethod将被触发去加载着个函数。当调用发现函数已经被加载，__index将不会被触发。<br />
下面有一个简单的实现自动加载的方法。每一个函数定义在一个辅助文件中。(也可能一个文件内有多个函数)这些文件中的每一个都以标准的方式定义函数，例如：<br />
function pack1.foo ()</p>
<p>&#8230;</p>
<p>end</p>
<p>function pack1.goo ()</p>
<p>&#8230;</p>
<p>end</p>
<p>然而，文件并不会创建package,因为当函数被加载的时候package已经存在了。<br />
在主package中我们定义一个辅助表来记录函数存放的位置：<br />
local location = {</p>
<p>foo = &#8220;/usr/local/lua/lib/pack1_1.lua&#8221;,</p>
<p>goo = &#8220;/usr/local/lua/lib/pack1_1.lua&#8221;,</p>
<p>foo1 = &#8220;/usr/local/lua/lib/pack1_2.lua&#8221;,</p>
<p>goo1 = &#8220;/usr/local/lua/lib/pack1_3.lua&#8221;,</p>
<p>}</p>
<p>下面我们创建package并且定义她的metamethod：<br />
pack1 = {}</p>
<p>setmetatable(pack1, {__index = function (t, funcname)</p>
<p>local file = location[funcname]</p>
<p>if not file then</p>
<p>error(&#8220;package pack1 does not define &#8221; .. funcname)</p>
<p>end</p>
<p>assert(loadfile(file))() &#8212; load and run definition</p>
<p>return t[funcname] &#8212; return the function</p>
<p>end})</p>
<p>return pack1</p>
<p>加载这个package之后，第一次程序执行pack1.foo() 将触发 __index metamethod, 接着发现函数有一个相应的文件，并加载这个文件。微妙之处在于：加载了文件，同时返回函数作为访问的结果。<br />
因为整个系统(译者：这里可能指复数吧？)都使用Lua写的，所以很容易改变系统的行为。例如，函数可以是用C写的，在metamethod中用loadlib加载他。或者我们我们可以在全局表中设定一个metamethod来自动加载整个packages.这里有无限的可能等着你去发掘。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/272/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》14.环境</title>
		<link>http://www.lifangjin.com/archives/271</link>
		<comments>http://www.lifangjin.com/archives/271#comments</comments>
		<pubDate>Thu, 22 Feb 2007 15:49:34 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=271</guid>
		<description><![CDATA[Lua用一个名为environment普通的表来保存所有的全局变量。(更精确的说，Lua在一系列的environment 中保存他的”global”变量，但是我们有时候可以忽略这种多样性) 这种结果的优点之一是他简化了Lua的内部实现，因为对于所有的全局变量没有必要非要有不同的数据结构。另一个(主要的)优点是我们可以像其他表一样操作这个保存全局变量的表。为了简化操作，Lua将环境本身存储在一个全局变量_G中，(_G._G等于_G)。例如，下面代码打印在当前环境中所有的全局变量的名字： for n in pairs(_G) do print(n) end 这一章我们将讨论一些如何操纵环境的有用的技术。 14.1使用动态名字访问全局变量 通常，赋值操作对于访问和修改全局变量已经足够。然而，我们经常需要一些原编程（meta-programming）的方式，比如当我们需要操纵一个名字被存储在另一个变量中的全局变量，或者需要在运行时才能知道的全局变量。为了获取这种全局变量的值，有的程序员可能写出下面类似的代码： loadstring(&#8220;value = &#8221; .. varname)() or value = loadstring(&#8220;return &#8221; .. varname)() 如果varname是 x, 上面连接操作的结果为： &#8220;return x&#8221; (第一种形式为 &#8220;value = x&#8221;), 当运行时才会产生最终的结果。然而这段代码涉及到一个新的chunk的创建和编译以及其他很多额外的问题。你可以换种方式更高效更简洁的完成同样的功能，代码如下： value = _G[varname] 因为环境是一个普通的表，所以你可以使用你需要获取的变量(变量名)索引表即可。 也可以用相似的方式对一个全局变量赋值：_G[varname] = value. 小心:一些程序员对这些函数很兴奋，并且可能写出这样的代码： _G["a"] = _G["var1"], 这只是a = var1的复杂的写法而已。 对前面的问题概括一下，表域可以是型如&#8221;io.read&#8221; or &#8220;a.b.c.d&#8221;的动态名字。我们用循环解决这个问题，从_G开始，一个域一个域的遍历： function getfield (f) [...]]]></description>
			<content:encoded><![CDATA[<p>Lua用一个名为environment普通的表来保存所有的全局变量。(更精确的说，Lua在一系列的environment 中保存他的”global”变量，但是我们有时候可以忽略这种多样性) 这种结果的优点之一是他简化了Lua的内部实现，因为对于所有的全局变量没有必要非要有不同的数据结构。另一个(主要的)优点是我们可以像其他表一样操作这个保存全局变量的表。为了简化操作，Lua将环境本身存储在一个全局变量_G中，(_G._G等于_G)。例如，下面代码打印在当前环境中所有的全局变量的名字：<br />
for n in pairs(_G) do print(n) end</p>
<p>这一章我们将讨论一些如何操纵环境的有用的技术。<br />
<span id="more-271"></span></p>
<p>14.1使用动态名字访问全局变量<br />
通常，赋值操作对于访问和修改全局变量已经足够。然而，我们经常需要一些原编程（meta-programming）的方式，比如当我们需要操纵一个名字被存储在另一个变量中的全局变量，或者需要在运行时才能知道的全局变量。为了获取这种全局变量的值，有的程序员可能写出下面类似的代码：<br />
loadstring(&#8220;value = &#8221; .. varname)()</p>
<p>or<br />
value = loadstring(&#8220;return &#8221; .. varname)()</p>
<p>如果varname是 x, 上面连接操作的结果为： &#8220;return x&#8221; (第一种形式为 &#8220;value = x&#8221;), 当运行时才会产生最终的结果。然而这段代码涉及到一个新的chunk的创建和编译以及其他很多额外的问题。你可以换种方式更高效更简洁的完成同样的功能，代码如下：<br />
value = _G[varname]</p>
<p>因为环境是一个普通的表，所以你可以使用你需要获取的变量(变量名)索引表即可。<br />
也可以用相似的方式对一个全局变量赋值：_G[varname] = value. 小心:一些程序员对这些函数很兴奋，并且可能写出这样的代码： _G["a"] = _G["var1"], 这只是a = var1的复杂的写法而已。<br />
对前面的问题概括一下，表域可以是型如&#8221;io.read&#8221; or &#8220;a.b.c.d&#8221;的动态名字。我们用循环解决这个问题，从_G开始，一个域一个域的遍历：<br />
function getfield (f)</p>
<p>local v = _G &#8212; start with the table of globals</p>
<p>for w in string.gfind(f, &#8220;[%w_]+&#8221;) do</p>
<p>v = v[w]</p>
<p>end</p>
<p>return v</p>
<p>end</p>
<p>我们使用string库的gfind函数来迭代f中的所有单词（单词指一个或多个子母下划线的序列）。 相对应的，设置一个域的函数稍微复杂些。赋值如：</p>
<p>a.b.c.d.e = v</p>
<p>实际等价于：<br />
local temp = a.b.c.d</p>
<p>temp.e = v</p>
<p>也就是说，我们必须记住最后一个名字，必须独立的处理最后一个域。新的setfield函数当其中的域(译者注：中间的域肯定是表)不存在的时候还需要创建中间表。<br />
function setfield (f, v)</p>
<p>local t = _G &#8212; start with the table of globals</p>
<p>for w, d in string.gfind(f, &#8220;([%w_]+)(.?)&#8221;) do</p>
<p>if d == &#8220;.&#8221; then &#8212; not last field?</p>
<p>t[w] = t[w] or {} &#8212; create table if absent</p>
<p>t = t[w] &#8212; get the table</p>
<p>else &#8212; last field</p>
<p>t[w] = v &#8212; do the assignment</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>这个新的模式匹配以变量w加上一个可选的点(保存在变量d中)的域。如果一个域名后面不允许跟上点，表明它是最后一个名字。（我们将在第20章讨论模式匹配问题）。使用上面的函数<br />
setfield(&#8220;t.x.y&#8221;, 10)</p>
<p>创建一个全局变量表t,另一个表t.x，并且对t.x.y赋值为10：<br />
print(t.x.y) &#8211;&gt; 10</p>
<p>print(getfield(&#8220;t.x.y&#8221;)) &#8211;&gt; 10</p>
<p>14.2声明全局变量<br />
全局变量不需要声明，虽然这对一些小程序来说很方便，但程序很大时，一个简单的拼写错误可能引起bug并且很难发现。然而，如果我们喜欢，我们可以改变这种行为。因为Lua所有的全局变量都保存在一个普通的表中，我们可以使用metatables来改变访问全局变量的行为。<br />
第一个方法如下：<br />
setmetatable(_G, {</p>
<p>__newindex = function (_, n)</p>
<p>error(&#8220;attempt to write to undeclared variable &#8220;..n, 2)</p>
<p>end,</p>
<p>__index = function (_, n)</p>
<p>error(&#8220;attempt to read undeclared variable &#8220;..n, 2)</p>
<p>end,</p>
<p>})</p>
<p>这样一来，任何企图访问一个不存在的全局变量的操作都会引起错误：<br />
&gt; a = 1</p>
<p>stdin:1: attempt to write to undeclared variable a</p>
<p>但是我们如何声明一个新的变量呢？使用rawset，可以绕过metamethod:<br />
function declare (name, initval)</p>
<p>rawset(_G, name, initval or false)</p>
<p>end</p>
<p>or 带有 false 是为了保证新的全局变量不会为 nil。注意：你应该在安装访问控制以前(before installing the access control)定义这个函数，否则将得到错误信息：毕竟你是在企图创建一个新的全局声明。只要刚才那个函数在正确的地方，你就可以控制你的全局变量了：<br />
&gt; a = 1</p>
<p>stdin:1: attempt to write to undeclared variable a</p>
<p>&gt; declare&#8221;a&#8221;</p>
<p>&gt; a = 1 &#8212; OK</p>
<p>但是现在，为了测试一个变量是否存在，我们不能简单的比较他是否为nil。如果他是nil访问将抛出错误。所以，我们使用rawget绕过metamethod:<br />
if rawget(_G, var) == nil then</p>
<p>&#8211; `var&#8217; is undeclared</p>
<p>&#8230;</p>
<p>end</p>
<p>改变控制允许全局变量可以为nil也不难，所有我们需要的是创建一个辅助表用来保存所有已经声明的变量的名字。不管什么时候metamethod被调用的时候，他会检查这张辅助表看变量是否已经存在。代码如下：<br />
local declaredNames = {}</p>
<p>function declare (name, initval)</p>
<p>rawset(_G, name, initval)</p>
<p>declaredNames[name] = true</p>
<p>end</p>
<p>setmetatable(_G, {</p>
<p>__newindex = function (t, n, v)</p>
<p>if not declaredNames[n] then</p>
<p>error(&#8220;attempt to write to undeclared var. &#8220;..n, 2)</p>
<p>else</p>
<p>rawset(t, n, v) &#8212; do the actual set</p>
<p>end</p>
<p>end,</p>
<p>__index = function (_, n)</p>
<p>if not declaredNames[n] then</p>
<p>error(&#8220;attempt to read undeclared var. &#8220;..n, 2)</p>
<p>else</p>
<p>return nil</p>
<p>end</p>
<p>end,</p>
<p>})</p>
<p>两种实现方式，代价都很小可以忽略不计的。第一种解决方法：metamethods在平常操作中不会被调用。第二种解决方法：他们可能被调用，不过当且仅当访问一个值为nil的变量时。<br />
14.3非全局的环境<br />
全局环境的一个问题是，任何修改都会影响你的程序的所有部分。例如，当你安装一个metatable去控制全局访问时，你的整个程序都必须遵循同一个指导方针。如果你想使用标准库，标准库中可能使用到没有声明的全局变量，你将碰到坏运。<br />
<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">Lua</a> 5.0 允许每个函数可以有自己的环境来改善这个问题，听起来这很奇怪；毕竟，全局变量表的目的就是为了全局性使用。然而在Section 15.4我们将看到这个机制带来很多有趣的结构，全局的值依然是随处可以获取的。<br />
可以使用setfenv函数来改变一个函数的环境。Setfenv接受函数和新的环境作为参数。除了使用函数本身，还可以指定一个数字表示栈顶的活动函数。数字1代表当前函数，数字2代表调用当前函数的函数(这对写一个辅助函数来改变他们调用者的环境是很方便的), 依此类推. 下面这段代码是企图应用setfenv失败的例子：<br />
a = 1 &#8212; create a global variable</p>
<p>&#8211; change current environment to a new empty table</p>
<p>setfenv(1, {})</p>
<p>print(a)</p>
<p>导致：<br />
stdin:5: attempt to call global `print&#8217; (a nil value)</p>
<p>(你必须在单独的chunk内运行这段代码，如果你在交互模式逐行运行他，每一行都是一个不同的函数，调用setfenv只会影响他自己的那一行。)一旦你改变了你的环境，所有全局访问都使用这个新的表，如果她为空，你就丢失所有你的全局变量，甚至_G，所以，你应该首先使用一些有用的值封装(populate)她，比如老的环境：<br />
a = 1 &#8212; create a global variable</p>
<p>&#8211; change current environment</p>
<p>setfenv(1, {_G = _G})</p>
<p>_G.print(a) &#8211;&gt; nil</p>
<p>_G.print(_G.a) &#8211;&gt; 1</p>
<p>现在，当你访问 &#8220;global&#8221; _G, 他的值为旧的环境，其中你可以使用print函数。<br />
你也可以使用继承封装(populate)你的新的环境：<br />
a = 1</p>
<p>local newgt = {} &#8212; create new environment</p>
<p>setmetatable(newgt, {__index = _G})</p>
<p>setfenv(1, newgt) &#8212; set it</p>
<p>print(a) &#8211;&gt; 1</p>
<p>在这段代码新的环境从旧的环境中继承了print和a；然而，任何赋值操作都对新表进行，不用担心误操作修改了全局变量表。另外，你仍然可以通过_G修改全局变量：<br />
&#8211; continuing previous code</p>
<p>a = 10</p>
<p>print(a) &#8211;&gt; 10</p>
<p>print(_G.a) &#8211;&gt; 1</p>
<p>_G.a = 20</p>
<p>print(_G.a) &#8211;&gt; 20</p>
<p>当你创建一个新的函数时，他从创建他的函数继承了环境变量。所以，如果一个chunk改变了他自己的环境，这个chunk所有在改变之后定义的函数都共享相同的环境，都会受到影响。这对创建命名空间是非常有用的机制，我们下一章将会看到。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/271/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》13.Metatables and Metamethods</title>
		<link>http://www.lifangjin.com/archives/270</link>
		<comments>http://www.lifangjin.com/archives/270#comments</comments>
		<pubDate>Thu, 22 Feb 2007 15:47:39 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=270</guid>
		<description><![CDATA[Lua中的table由于定义的行为，我们可以对key-value对执行加操作，访问key对应的value，遍历所有的key-value。但是我们不可以对两个table执行加操作，也不可以比较两个表的大小。 Metatables允许我们改变table的行为，例如，使用Metatables我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时，他会检查两个表是否有一个表有Metatable，并且检查Metatable是否有__add域。如果找到则调用这个__add函数(所谓的Metamethod)去计算结果。 Lua中的每一个表都有其Metatable。(后面我们将看到userdata也有Metatable)，Lua默认创建一个不带metatable的新表 t = {} print(getmetatable(t)) &#8211;&#62; nil 可以使用setmetatable函数设置或者改变一个表的metatable t1 = {} setmetatable(t, t1) assert(getmetatable(t) == t1) 任何一个表都可以是其他一个表的metatable，一组相关的表可以共享一个metatable（描述他们共同的行为）。一个表也可以是自身的metatable（描述其私有行为）。 13．1算术运算的Metamethods 这一部分我们通过一个简单的例子介绍如何使用metamethods。假定我们使用table来描述结合，使用函数来描述集合的并操作，交集操作，like操作。我们在一个表内定义这些函数，然后使用构造函数创建一个集合: Set = {} function Set.new (t) local set = {} for _, l in ipairs(t) do set[l] = true end return set end function Set.union (a,b) local res = Set.new{} for k in pairs(a) [...]]]></description>
			<content:encoded><![CDATA[<p>Lua中的table由于定义的行为，我们可以对key-value对执行加操作，访问key对应的value，遍历所有的key-value。但是我们不可以对两个table执行加操作，也不可以比较两个表的大小。<br />
Metatables允许我们改变table的行为，例如，使用Metatables我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时，他会检查两个表是否有一个表有Metatable，并且检查Metatable是否有__add域。如果找到则调用这个__add函数(所谓的Metamethod)去计算结果。<br />
Lua中的每一个表都有其Metatable。(后面我们将看到userdata也有Metatable)，Lua默认创建一个不带metatable的新表<br />
t = {}<br />
print(getmetatable(t)) &#8211;&gt; nil<br />
可以使用setmetatable函数设置或者改变一个表的metatable<br />
t1 = {}<br />
setmetatable(t, t1)<br />
assert(getmetatable(t) == t1)<br />
任何一个表都可以是其他一个表的metatable，一组相关的表可以共享一个metatable（描述他们共同的行为）。一个表也可以是自身的metatable（描述其私有行为）。</p>
<p><span id="more-270"></span><br />
13．1算术运算的Metamethods<br />
这一部分我们通过一个简单的例子介绍如何使用metamethods。假定我们使用table来描述结合，使用函数来描述集合的并操作，交集操作，like操作。我们在一个表内定义这些函数，然后使用构造函数创建一个集合:<br />
Set = {}</p>
<p>function Set.new (t)<br />
local set = {}<br />
for _, l in ipairs(t) do set[l] = true end<br />
return set<br />
end</p>
<p>function Set.union (a,b)<br />
local res = Set.new{}<br />
for k in pairs(a) do res[k] = true end<br />
for k in pairs(b) do res[k] = true end<br />
return res<br />
end</p>
<p>function Set.intersection (a,b)<br />
local res = Set.new{}<br />
for k in pairs(a) do<br />
res[k] = b[k]<br />
end<br />
return res<br />
end<br />
为了帮助理解程序运行结果，我们也定义了打印函数输出结果:<br />
function Set.tostring (set)<br />
local s = &#8220;{&#8221;<br />
local sep = &#8220;&#8221;<br />
for e in pairs(set) do<br />
s = s .. sep .. e<br />
sep = &#8220;, &#8221;<br />
end<br />
return s .. &#8220;}&#8221;<br />
end</p>
<p>function Set.print (s)<br />
print(Set.tostring(s))<br />
end<br />
现在我们想加号运算符(+)执行两个集合的并操作，我们将所有集合共享一个metatable，并且为这个metatable添加如何处理相加操作。<br />
第一步，我们定义一个普通的表，用来作为metatable。为避免污染命名空间，我们将其放在set内部。<br />
Set.mt = {} &#8212; metatable for sets<br />
第二步，修改set.new函数，增加一行，创建表的时候同时指定对应的metatable.<br />
function Set.new (t) &#8212; 2nd version<br />
local set = {}<br />
setmetatable(set, Set.mt)<br />
for _, l in ipairs(t) do set[l] = true end<br />
return set<br />
end<br />
这样一来，set.new创建的所有的集合都有相同的metatable了：<br />
s1 = Set.new{10, 20, 30, 50}<br />
s2 = Set.new{30, 1}<br />
print(getmetatable(s1)) &#8211;&gt; table: 00672B60<br />
print(getmetatable(s2)) &#8211;&gt; table: 00672B60<br />
第三步，给metatable增加__add函数。<br />
Set.mt.__add = Set.union<br />
当Lua试图对两个集合相加时，将调用这个函数，以两个相加的表作为参数。<br />
通过metamethod，我们可以对两个集合进行相加：<br />
s3 = s1 + s2<br />
Set.print(s3) &#8211;&gt; {1, 10, 20, 30, 50}<br />
同样的我们可以使用相乘运算符来定义集合的交集操作<br />
Set.mt.__mul = Set.intersection</p>
<p>Set.print((s1 + s2)*s1) &#8211;&gt; {10, 20, 30, 50}<br />
对于每一个算术运算符，metatable都有对应的域名与其对应，除了__add,__mul外，还有__sub(减),__div(除),__unm(负),__pow(幂)，我们也可以定义__concat定义连接行为。<br />
当我们对两个表进行加没有问题，但如果两个操作数有不同的metatable例如：<br />
s = Set.new{1,2,3}<br />
s = s + 8<br />
Lua选择metamethod的原则：如果第一个参数存在带有__add域的metatable，Lua使用它作为metamethod，和第二个参数无关;<br />
否则第二个参数存在带有__add域的metatable，Lua使用它作为metamethod 否则报错。<br />
Lua不关心这种混合类型的，如果我们运行上面的s=s+8的例子在Set.union发生错误:<br />
bad argument #1 to `pairs&#8217; (table expected, got number)<br />
如果我们想得到更加清楚地错误信息，我们需要自己显式的检查操作数的类型：<br />
function Set.union (a,b)<br />
if getmetatable(a) ~= Set.mt or<br />
getmetatable(b) ~= Set.mt then<br />
error(&#8220;attempt to `add&#8217; a set with a non-set value&#8221;, 2)<br />
end<br />
&#8230; &#8212; same as before</p>
<p>13．2关系运算的Metamethods<br />
Metatables 也允许我们使用metamethods: __eq (等于), __lt (小于), and __le (小于等于)给关系运算符赋予特殊的含义. 对剩下的三个关系运算符没有专门的metamethod,因为Lua 将a ~= b 转换为 not (a == b), a &gt; b 转换为 b &lt; a, a &gt;= b 转换为 b &lt;= a.<br />
(外层的圆括号: 直到Lua 4.0为止, 所有的比较运算符被转换成一个,a &lt;= b 转为not (b &lt; a). 然而这种转换并不一致正确,当我们遇到偏序(partial order)情况,也就是说,并不是所有的元素都可以正确的被排序情况。例如,在大多数机器上浮点数不能被排序,因为他的值不是一个数字(Not a Number即NaN)。根据IEEE 754 的标准，NaN表示一个未定义的值，比如0/0的结果。该标准指出任何涉及到NaN比较的结果都应为false。也就是说,NaN &lt;= x 总是false,x &lt; NaN 也总是false. 这样一来，在这种情况下a &lt;= b 转换为 not (b &lt; a)就不再正确了。：外层圆括号结束)<br />
在我们关于基和操作的例子中，有类似的问题存在。&lt;=代表集合的包含: a &lt;= b 表示集合a是集合b的子集.这种意义下，可能 a &lt;= b 和b &lt; a 都是false; 因此, 我们需要将__le 和__lt 的实现分开:<br />
Set.mt.__le = function (a,b) &#8212; set containment<br />
for k in pairs(a) do<br />
if not b[k] then return false end<br />
end<br />
return true<br />
end</p>
<p>Set.mt.__lt = function (a,b)<br />
return a &lt;= b and not (b &lt;= a)<br />
end<br />
最后，我们通过集合的包含来定义集合相等:<br />
Set.mt.__eq = function (a,b)<br />
return a &lt;= b and b &lt;= a<br />
end<br />
有了上面的定义之后，现在我们就可以来比较集合了:<br />
s1 = Set.new{2, 4}<br />
s2 = Set.new{4, 10, 2}<br />
print(s1 &lt;= s2) &#8211;&gt; true<br />
print(s1 &lt; s2) &#8211;&gt; true<br />
print(s1 &gt;= s1) &#8211;&gt; true<br />
print(s1 &gt; s1) &#8211;&gt; false<br />
print(s1 == s2 * s1) &#8211;&gt; true<br />
与算术运算的metamethods不同, 关系元算的metamethods不支持混合类型运算.对于混合类型比较运算的处理方法和Lua的公共行为类似。如果你试图比较一个字符串和一个数字，Lua将抛出错误.相似的，如果你试图比较两个带有不同metamethods的对象，Lua也将抛出错误。<br />
但相等比较从来不会抛出错误，如果两个对象有不同的metamethod，比较的结果为false，甚至可能不会调用metamethod。这也是模仿了Lua的公共的行为，因为Lua总是认为字符串和数字是不等的，而不去判断它们的值。仅当两个有共同的metamethod的对象进行相等比较的时候，Lua才会调用对应的metamethod。</p>
<p>13．3库定义的 Metamethods<br />
在一些库中，在自己的metatables中定义自己的域是很普遍的情况。到目前为止，我们看到的所有metamethods都是Lua核心部分的。有虚拟机负责处理运算符涉及到的metatables和为运算符定义操作的metamethods。但是，metatable是一个普通的表，任何人都可以使用。<br />
tostring 是一个典型的例子。如前面我们所见，tostring以简单的格式表示出table:<br />
print({}) &#8211;&gt; table: 0x8062ac0<br />
(注意：print函数总是调用tostring 来格式化它的输出).然而当格式化一个对象的时候, tostring会首先检查对象是否存在一个带有__tostring域的metatable。如果存在则以对象作为参数调用对应的函数来完成格式化，返回的结果即为tostring的结果。<br />
在我们集合的例子中我们已经定义了一个函数来将集合转换成字符串打印出来。因此，我们只需要将集合的metatable的__tostring域调用我们定义的打印函数:<br />
Set.mt.__tostring = Set.tostring<br />
这样，不管什么时候我们调用print打印一个集合，print都会自动调用tostring，而tostring则会调用Set.tostring :<br />
s1 = Set.new{10, 4, 5}<br />
print(s1) &#8211;&gt; {4, 5, 10}<br />
setmetatable/getmetatable函数也会使用metafield，在这种情况下，可以保护metatables。假定你想保护你的集合使其使用者既看不到也不能修改metatables。如果你对metatable设置了__metatable的值，getmetatable 将返回这个域的值，而调用setmetatable 将会出错:<br />
Set.mt.__metatable = &#8220;not your business&#8221;</p>
<p>s1 = Set.new{}<br />
print(getmetatable(s1)) &#8211;&gt; not your business<br />
setmetatable(s1, {})<br />
stdin:1: cannot change protected metatable</p>
<p>13．4表相关的Metamethods<br />
关于算术运算和关系元算的metamethods都定义了错误状态的行为，他们并不改变语言本身的行为。针对在两种正常状态：表的不存在的域的查询和修改，Lua也提供了改变tables的行为的方法。</p>
<p>13．4．1The __index Metamethod<br />
前面说过，当我们访问一个表的不存在的域，返回结果为nil，这是正确的，但并不一致正确。实际上，这种访问触发lua解释器去查找__index metamethod: 如果不存在，返回结果为nil; 如果存在则由__index metamethod 返回结果.<br />
这个例子的原型是一种继承。假设我们想创建一些表来描述窗口。每一个表必须描述窗口的一些参数，比如：位置，大小，颜色风格等等。所有的这些参数都有默认的值，当我们想要创建窗口的时候只需要给出非默认值的参数即可创建我们需要的窗口。第一种方法是，实现一个表的构造器，对这个表内的每一个缺少域都填上默认值。第二种方法是，创建一个新的窗口去继承一个原型窗口的缺少域。首先，我们实现一个原型和一个构造函数，他们共享一个metatable:<br />
&#8211; create a namespace<br />
Window = {}<br />
&#8211; create the prototype with default values<br />
Window.prototype = {x=0, y=0, width=100, height=100, }<br />
&#8211; create a metatable<br />
Window.mt = {}<br />
&#8211; declare the constructor function<br />
function Window.new (o)<br />
setmetatable(o, Window.mt)<br />
return o<br />
end<br />
现在我们定义 __index metamethod:<br />
Window.mt.__index = function (table, key)<br />
return Window.prototype[key]<br />
end<br />
这样一来，我们创建一个新的窗口，然后访问他缺少的域结果如下：<br />
w = Window.new{x=10, y=20}<br />
print(w.width) &#8211;&gt; 100<br />
当Lua发现w不存在域width时，但是有一个metatable带有__index域，Lua使用w(the table)和width(缺少的值) 来调用__index metamethod，metamethod则通过访问原型表(prototype)获取缺少的域的结果。<br />
__index metamethod在继承中的使用非常常见，所以Lua提供了一个更简洁的使用方式。__index metamethod不需要非是一个函数，他也可以是一个表。但它是一个函数的时候，Lua将table和缺少的域作为参数调用这个函数；当他是一个表的时候，Lua将在这个表中看是否有缺少的域。所以，上面的那个例子可以使用第二种方式简单的改写为：<br />
Window.mt.__index = Window.prototype<br />
现在，当Lua查找metatable的__index域时，他发现window.prototype的值，它是一个表，所以Lua将访问这个表来获取缺少的值，也就是说它相当于执行：<br />
Window.prototype["width"]<br />
将一个表作为__index metamethod使用，提供了一种廉价而简单的实现单继承的方法。一个函数的代价虽然稍微高点，但提供了更多的灵活性：我们可以实现多继承，隐藏，和其他一些变异的机制。我们将在第16章详细的讨论继承的方式。<br />
当我们想不通过调用__index metamethod来访问一个表，我们可以使用rawget函数。Rawget(t,i)的调用以raw access方式访问表。这种访问方式不会使你的代码变快（the overhead of a function call kills any gain you could have），但有些时候我们需要他，在后面我们将会看到。<br />
13．4．2The __newindex Metamethod<br />
__newindex metamethod用来对表更新，__index则用来对表访问。当你给表的一个缺少的域赋值，解释器就会查找__newindex metamethod:如果存在则调用这个函数而不进行赋值操作。像__index一样，如果metamethod是一个表，解释器对指定的那个表，而不是原始的表进行赋值操作。另外，有一个raw 函数可以绕过metamethod：调用rawset(t,k,v)不掉用任何metamethod对表t的k域赋值为v。 __index 和 __newindex metamethods的混合使用提供了强大的结构：从只读表到面向对象编程的带有继承默认值的表。在这一张的剩余部分我们看一些这些应用的例子，面向对象的编程在另外的章节介绍。<br />
13．4．3有默认值的表<br />
在一个普通的表中任何域的默认值都是nil。很容易通过metatables来改变默认值：　<br />
function setDefault (t, d)<br />
local mt = {__index = function () return d end}<br />
setmetatable(t, mt)<br />
end</p>
<p>tab = {x=10, y=20}<br />
print(tab.x, tab.z) &#8211;&gt; 10 nil<br />
setDefault(tab, 0)<br />
print(tab.x, tab.z) &#8211;&gt; 10 0<br />
现在，不管什么时候我们访问表的缺少的域，他的__index metamethod被调用并返回0。 setDefault函数为每一个需要默认值的表创建了一个新的metatable。在有很多的表需要默认值的情况下，这可能使得花费的代价变大。然而metatable有一个默认值d和它本身关联，所以函数不能为所有表使用单一的一个metatable。为了避免带有不同默认值的所有的表使用单一的metatable，我们将每个表的默认值，使用一个唯一的域存储在表本身里面。如果我们不担心命名的混乱，我可使用像&#8221;___&#8221;作为我们的唯一的域：<br />
local mt = {__index = function (t) return t.___ end}<br />
function setDefault (t, d)<br />
t.___ = d<br />
setmetatable(t, mt)<br />
end<br />
如果我们担心命名混乱，也很容易保证这个特殊的键值唯一性。我们要做的只是创建一个新表用作键值：<br />
local key = {} &#8212; unique key<br />
local mt = {__index = function (t) return t[key] end}<br />
function setDefault (t, d)<br />
t[key] = d<br />
setmetatable(t, mt)<br />
end<br />
另外一种解决表和默认值关联的方法是使用一个分开的表来处理，在这个特殊的表中索引是表，对应的值为默认值。然而这种方法的正确实现我们需要一种特殊的表：weak table，到目前为止我们还没有介绍这部分内容，将在第17章讨论。<br />
为了带有不同默认值的表可以重用相同的原表，还有一种解决方法是使用memoize metatables， 然而这种方法也需要weak tables，所以我们再次不得不等到第17章。</p>
<p>13．4．4监控表<br />
__index 和__newindex都是只有当表中访问的域不存在时候才起作用。捕获对一个表的所有访问情况的唯一方法就是保持表为空。因此，如果我们想监控一个表的所有访问情况，我们应该为真实的表创建一个代理。这个代理是一个空表，并且带有__index和 __newindex metamethods，由这两个方法负责跟踪表的所有访问情况并将其指向原始的表。假定，t是我们想要跟踪的原始表，我们可以：<br />
t = {} &#8212; original table (created somewhere)</p>
<p>&#8211; keep a private access to original table<br />
local _t = t</p>
<p>&#8211; create proxy<br />
t = {}</p>
<p>&#8211; create metatable<br />
local mt = {<br />
__index = function (t,k)<br />
print(&#8220;*access to element &#8221; .. tostring(k))<br />
return _t[k] &#8212; access the original table<br />
end,</p>
<p>__newindex = function (t,k,v)<br />
print(&#8220;*update of element &#8221; .. tostring(k) ..<br />
&#8221; to &#8221; .. tostring(v))<br />
_t[k] = v &#8212; update original table<br />
end<br />
}<br />
setmetatable(t, mt)<br />
这段代码将跟踪所有对t的访问情况：<br />
&gt; t[2] = &#8216;hello&#8217;<br />
*update of element 2 to hello<br />
&gt; print(t[2])<br />
*access to element 2<br />
hello<br />
(注意：不幸的是，这个设计不允许我们遍历表。Pairs函数将对proxy进行操作，而不是原始的表。) 如果我们想监控多张表，我们不需要为每一张表都建立一个不同的metatable。我们只要将每一个proxy和他原始的表关联，所有的proxy共享一个公用的metatable即可。将表和对应的proxy关联的一个简单的方法是将原始的表作为proxy的域，只要我们保证这个域不用作其他用途。一个简单的保证它不被作他用的方法是创建一个私有的没有他人可以访问的key。将上面的思想汇总，最终的结果如下：<br />
&#8211; create private index<br />
local index = {}</p>
<p>&#8211; create metatable<br />
local mt = {<br />
__index = function (t,k)<br />
print(&#8220;*access to element &#8221; .. tostring(k))<br />
return t[index][k] &#8212; access the original table<br />
end,</p>
<p>__newindex = function (t,k,v)<br />
print(&#8220;*update of element &#8221; .. tostring(k) ..<br />
&#8221; to &#8221; .. tostring(v))<br />
t[index][k] = v &#8212; update original table<br />
end<br />
}</p>
<p>function track (t)<br />
local proxy = {}<br />
proxy[index] = t<br />
setmetatable(proxy, mt)<br />
return proxy<br />
end<br />
现在，不管什么时候我们想监控表t，我们要做得只是t=track(t)。</p>
<p>13．4．5只读表<br />
采用代理的思想很容易实现一个只读表。我们需要做得只是当我们监控到企图修改表时候抛出错误。通过 __index metamethod,我们可以不使用函数而是用原始表本身来使用表，因为我们不需要监控查寻。这是比较简单并且高效的重定向所有查询到原始表的方法。但是，这种用法要求每一个只读代理有一个单独的新的metatable，使用__index指向原始表:<br />
function readOnly (t)<br />
local proxy = {}<br />
local mt = { &#8212; create metatable<br />
__index = t,<br />
__newindex = function (t,k,v)<br />
error(&#8220;attempt to update a read-only table&#8221;, 2)<br />
end<br />
}<br />
setmetatable(proxy, mt)<br />
return proxy<br />
end<br />
(记住：error的第二个参数2，将错误信息返回给企图执行update的地方。) 作为一个简单的例子，我们对工作日建立一个只读表：<br />
days = readOnly{&#8220;Sunday&#8221;, &#8220;Monday&#8221;, &#8220;Tuesday&#8221;, &#8220;Wednesday&#8221;,<br />
&#8220;Thursday&#8221;, &#8220;Friday&#8221;, &#8220;Saturday&#8221;}</p>
<p>print(days[1]) &#8211;&gt; Sunday<br />
days[2] = &#8220;Noday&#8221;<br />
stdin:1: attempt to update a read-only table</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/270/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》12. Data Files and Persistence</title>
		<link>http://www.lifangjin.com/archives/265</link>
		<comments>http://www.lifangjin.com/archives/265#comments</comments>
		<pubDate>Mon, 19 Feb 2007 12:45:39 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=265</guid>
		<description><![CDATA[当我们处理数据文件的, 一般来说，写文件比读取文件内容来的容易.因为我们可以很好的控制文件的写操作，而从文件读取数据常常碰到不可预知的情况.一个健壮的程序不仅应该可以读取存有正确格式的数据还应该能够处理坏文件(译者注：对数据内容和格式进行校验，对异常情况能够做出恰当处理).正因为如此，实现一个健壮的读取数据文件的程序是很困难的. 正如我们在Section 10.1(译者：第10章Complete Examples)中看到的例子,文件格式可以通过使用Lua中的table构造器来描述.我们只需要在写数据的稍微做一些做一点额外的工作，读取数据将变得容易很多.方法是：将我们的数据文件内容作为Lua代码写到Lua程序中去.通过使用table构造器，这些存放在Lua代码中的数据可以像其他普通的文件一样看起来引人注目. 为了更清楚地描述问题，下面我们看看例子.如果我们的数据是预先确定的格式，比如CSV(逗号分割值),我们几乎没得选择.(在第20章，我们介绍如何在Lua中处理CSV文件).但是如果我们打算创建一个文件为了将来使用，除了CSV，我们可以使用Lua构造器来我们表述我们数据，这种情况下，我们将每一个数据记录描述为一个Lua构造器. 将下面的代码 Donald E. Knuth,Literate Programming,CSLI,1992 Jon Bentley,More Programming Pearls,Addison-Wesley,1990 写成 Entry{&#8220;Donald E. Knuth&#8221;, &#8220;Literate Programming&#8221;, &#8220;CSLI&#8221;, 1992} Entry{&#8220;Jon Bentley&#8221;, &#8220;More Programming Pearls&#8221;, &#8220;Addison-Wesley&#8221;, 1990} 记住Entry{&#8230;} 与Entry({&#8230;})等价,他是一个以表作为唯一参数的函数调用.所以，前面那段数据在Lua程序中表示如上.如果要读取这个段数据，我们只需要运行我们的Lua代码.例如下面这段代码计算数据文件中记录数: local count = 0 function Entry (b) count = count + 1 end dofile(&#8220;data&#8221;) print(&#8220;number of entries: &#8221; .. count) 下面这段程序收集一个作者名列表中的名字是否在数据文件中出现，如果在文件中出现则打印出来. (作者名字是Entry的第一个域; 所以, [...]]]></description>
			<content:encoded><![CDATA[<p>当我们处理数据文件的, 一般来说，写文件比读取文件内容来的容易.因为我们可以很好的控制文件的写操作，而从文件读取数据常常碰到不可预知的情况.一个健壮的程序不仅应该可以读取存有正确格式的数据还应该能够处理坏文件(译者注：对数据内容和格式进行校验，对异常情况能够做出恰当处理).正因为如此，实现一个健壮的读取数据文件的程序是很困难的.<br />
正如我们在Section 10.1(译者：第10章Complete Examples)中看到的例子,文件格式可以通过使用Lua中的table构造器来描述.我们只需要在写数据的稍微做一些做一点额外的工作，读取数据将变得容易很多.方法是：将我们的数据文件内容作为Lua代码写到Lua程序中去.通过使用table构造器，这些存放在Lua代码中的数据可以像其他普通的文件一样看起来引人注目.<br />
<span id="more-265"></span><br />
为了更清楚地描述问题，下面我们看看例子.如果我们的数据是预先确定的格式，比如CSV(逗号分割值),我们几乎没得选择.(在第20章，我们介绍如何在Lua中处理CSV文件).但是如果我们打算创建一个文件为了将来使用，除了CSV，我们可以使用Lua构造器来我们表述我们数据，这种情况下，我们将每一个数据记录描述为一个Lua构造器. 将下面的代码<br />
Donald E. Knuth,Literate Programming,CSLI,1992<br />
Jon Bentley,More Programming Pearls,Addison-Wesley,1990<br />
写成<br />
Entry{&#8220;Donald E. Knuth&#8221;,<br />
&#8220;Literate Programming&#8221;,<br />
&#8220;CSLI&#8221;,<br />
1992}</p>
<p>Entry{&#8220;Jon Bentley&#8221;,<br />
&#8220;More Programming Pearls&#8221;,<br />
&#8220;Addison-Wesley&#8221;,<br />
1990}<br />
记住Entry{&#8230;} 与Entry({&#8230;})等价,他是一个以表作为唯一参数的函数调用.所以，前面那段数据在Lua程序中表示如上.如果要读取这个段数据，我们只需要运行我们的Lua代码.例如下面这段代码计算数据文件中记录数:<br />
local count = 0<br />
function Entry (b) count = count + 1 end<br />
dofile(&#8220;data&#8221;)<br />
print(&#8220;number of entries: &#8221; .. count)<br />
下面这段程序收集一个作者名列表中的名字是否在数据文件中出现，如果在文件中出现则打印出来. (作者名字是Entry的第一个域; 所以, 如果b 是一个entry 的值, b[1]则代表作者名.)<br />
local authors = {} &#8212; a set to collect authors<br />
function Entry (b) authors[b[1]] = true end<br />
dofile(&#8220;data&#8221;)<br />
for name in pairs(authors) do print(name) end<br />
注意，在这些程序段中使用事件驱动的方法：Entry函数作为回调函数，dofile处理数据文件中的每一记录都回调用它. 当数据文件的大小不是太大的情况下，我们可以使用name-value对来描述数据：<br />
Entry{<br />
author = &#8220;Donald E. Knuth&#8221;,<br />
title = &#8220;Literate Programming&#8221;,<br />
publisher = &#8220;CSLI&#8221;,<br />
year = 1992<br />
}</p>
<p>Entry{<br />
author = &#8220;Jon Bentley&#8221;,<br />
title = &#8220;More Programming Pearls&#8221;,<br />
publisher = &#8220;Addison-Wesley&#8221;,<br />
year = 1990<br />
}<br />
(如果这种格式让你想起BibTeX,这并不奇怪.Lua中构造器正是根据来自BibTeX的灵感实现的.) 这种格式我们称之为自描述数据格式，因为每一个数据段都根据他的意思简短的描述为一种数据格式.相对CSV和其他紧缩格式，自描述数据格式更容易阅读和理解，当需要修改的时候可以容易的手工编辑，而且不需要改动数据文件.例如，如果我们想增加一个域，只需要对读取程序稍作修改即可，当指定的域不存在时，也可以赋予默认值. 使用name-value对描述的情况下,上面收集作者名的代码可以改写为：<br />
local authors = {} &#8212; a set to collect authors<br />
function Entry (b) authors[b.author] = true end<br />
dofile(&#8220;data&#8221;)<br />
for name in pairs(authors) do print(name) end<br />
现在，记录域的顺序无关紧要了，甚至某些记录即使不存在author这个域，我们也只需要稍微改动一下代码即可:<br />
function Entry (b)<br />
if b.author then authors[b.author] = true end<br />
end<br />
Lua不仅运行速度快，编译速度也快.例如，上面这段搜集作者名的代码处理一个2MB的数据文件时间不会超过1秒.另外，这不是偶然的，数据描述是Lua的主要应用之一，从Lua发明以来，我们花了很多心血使他能够更快的编译和运行大的chunks.<br />
12.1序列化<br />
我们经常需要序列化一些数据，为了将数据转换为字节流或者字符流,这样我们就可以保存到文件或者通过网络发送出去.我们可以在Lua代码中描述序列化的数据，在这种方式下，我们运行读取程序即可从代码中构造出保存的值.<br />
通常，我们使用这样的方式varname = <exp></exp>来保存一个全局变量的值.varname部分比较容易理解，下面我们来看看如何写一个产生值的代码.对于一个数值来说:<br />
function serialize (o)<br />
if type(o) == &#8220;number&#8221; then<br />
io.write(o)<br />
else &#8230;<br />
end<br />
对于字符串值而言，原始的写法应该是:<br />
if type(o) == &#8220;string&#8221; then<br />
io.write(&#8220;&#8216;&#8221;, o, &#8220;&#8216;&#8221;)<br />
然而，如果字符串包含特殊字符(比如引号或者换行符)，产生的代码将不是有效的Lua程序.这时候你可能用下面方法解决特殊字符的问题：<br />
if type(o) == &#8220;string&#8221; then<br />
io.write(&#8220;[[", o, "]]&#8221;)<br />
千万不要这样做！双引号是针对手写的字符串的而不是针对自动产生的字符串.如果有人恶意的引导你的程序去使用&#8221; ]]..os.execute(&#8216;rm *&#8217;)..[[ "这样的方式去保存某些东西(比如它可能提供字符串作为地址)你最终的chunk将是这个样子:<br />
varname = [[ ]]..os.execute(&#8216;rm *&#8217;)..[[ ]]<br />
如果你load这个数据，运行结果可想而知的. 为了以安全的方式引用任意的字符串，string标准库提供了格式化函数专门提供&#8221;%q&#8221;选项.它可以使用双引号表示字符串并且可以正确的处理包含引号和换行等特殊字符的字符串.这样一来，我们的序列化函数可以写为:<br />
function serialize (o)<br />
if type(o) == &#8220;number&#8221; then<br />
io.write(o)<br />
elseif type(o) == &#8220;string&#8221; then<br />
io.write(string.format(&#8220;%q&#8221;, o))<br />
else &#8230;<br />
end<br />
12.1.1保存不带循环的table<br />
我们下一个艰巨的任务是保存表.根据表的结构不同，采取的方法也有很多.没有一种单一的算法对所有情况都能很好地解决问题.简单的表不仅需要简单的算法而且结果文件也需要看起来也更美观.<br />
我们第一次尝试如下:<br />
function serialize (o)<br />
if type(o) == &#8220;number&#8221; then<br />
io.write(o)<br />
elseif type(o) == &#8220;string&#8221; then<br />
io.write(string.format(&#8220;%q&#8221;, o))<br />
elseif type(o) == &#8220;table&#8221; then<br />
io.write(&#8220;{\n&#8221;)<br />
for k,v in pairs(o) do<br />
io.write(&#8221; &#8220;, k, &#8221; = &#8220;)<br />
serialize(v)<br />
io.write(&#8220;,\n&#8221;)<br />
end<br />
io.write(&#8220;}\n&#8221;)<br />
else<br />
error(&#8220;cannot serialize a &#8221; .. type(o))<br />
end<br />
end<br />
尽管他很简单，但他的确很好的解决了问题.只要表结构是一个树型结构(也就是说，没有共享的子表并且没有循环),他甚至可以处理嵌套表(表中表).对于所进不整齐的表我们可以少作改进使结果更美观，这可以作为一个练习尝试一下.(提示：增加一个参数表示缩进的字符串，来进行序列化). 前面的函数假定表中出现的所有关键字都是合法的标示符.如果表中有不符合Lua语法的数字关键字或者字符串关键字,上面的代码将碰到麻烦.一个简单的解决这个难题的方法是将：<br />
io.write(&#8221; &#8220;, k, &#8221; = &#8220;)<br />
改为<br />
io.write(&#8221; [")<br />
serialize(k)<br />
io.write("] = &#8220;)<br />
这样一来，我们改善了我们的函数的健壮性，比较一下两次的结果:<br />
&#8211; result of serialize{a=12, b=&#8217;<a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">Lua</a>&#8217;, key=&#8217;another &#8220;one&#8221;&#8216;}<br />
&#8211; 第一个版本<br />
{<br />
a = 12,<br />
b = &#8220;Lua&#8221;,<br />
key = &#8220;another \&#8221;one\&#8221;",<br />
}</p>
<p>&#8211; 第二个版本<br />
{<br />
["a"] = 12,<br />
["b"] = &#8220;Lua&#8221;,<br />
["key"] = &#8220;another \&#8221;one\&#8221;",<br />
}<br />
我们可以通过测试每一种情况，看是否需要方括号，另外，我们将这个问题留作一个练习给大家．<br />
12.1.2保存带有循环的table<br />
针对普通拓扑概念上的带有循环表和共享子表的table，我们需要另外一种不同的方法来处理。构造器不能很好地解决这种情况，我们不使用。为了表示循环我们需要将表名记录下来，下面我们的函数有两个参数：table和对应的名字。另外，我们还必须记录已经保存过的table以防止由于循环而被重复保存。我们使用一个额外的table来记录保存过的表的轨迹，这个表的下表索引为table，而值为对应的表名。<br />
我们做一个限制：要保存的table只有一个字符串或者数字关键字。下面的这个函数序列化基本类型并返回结果。<br />
function basicSerialize (o)<br />
if type(o) == &#8220;number&#8221; then<br />
return tostring(o)<br />
else &#8212; assume it is a string<br />
return string.format(&#8220;%q&#8221;, o)<br />
end<br />
end<br />
关键内容在接下来的这个函数，saved这个参数是上面提到的记录已经保存的表的踪迹的table。<br />
function save (name, value, saved)<br />
saved = saved or {} &#8212; initial value<br />
io.write(name, &#8221; = &#8220;)<br />
if type(value) == &#8220;number&#8221; or type(value) == &#8220;string&#8221; then<br />
io.write(basicSerialize(value), &#8220;\n&#8221;)<br />
elseif type(value) == &#8220;table&#8221; then<br />
if saved[value] then &#8212; value already saved?<br />
io.write(saved[value], &#8220;\n&#8221;) &#8212; use its previous name<br />
else<br />
saved[value] = name &#8212; save name for next time<br />
io.write(&#8220;{}\n&#8221;) &#8212; create a new table<br />
for k,v in pairs(value) do &#8212; save its fields<br />
local fieldname = string.format(&#8220;%s[%s]&#8220;, name,<br />
basicSerialize(k))<br />
save(fieldname, v, saved)<br />
end<br />
end<br />
else<br />
error(&#8220;cannot save a &#8221; .. type(value))<br />
end<br />
end<br />
举个例子：<br />
我们将要保存的table为：<br />
a = {x=1, y=2; {3,4,5}}<br />
a[2] = a &#8212; cycle<br />
a.z = a[1] &#8212; shared sub-table<br />
调用save(&#8216;a&#8217;, a)之后结果为：<br />
a = {}<br />
a[1] = {}<br />
a[1][1] = 3<br />
a[1][2] = 4<br />
a[1][3] = 5</p>
<p>a[2] = a<br />
a["y"] = 2<br />
a["x"] = 1<br />
a["z"] = a[1]<br />
(实际的顺序可能有所变化，它依赖于table遍历的顺序，不过，这个算法保证了一个新的定义中需要的前面的节点都已经被定义过)<br />
如果我们想保存带有共享部分的表，我们可以使用同样table的saved参数调用save函数，例如我们创建下面两个表：<br />
a = {{&#8220;one&#8221;, &#8220;two&#8221;}, 3}<br />
b = {k = a[1]}<br />
保存他们：<br />
save(&#8216;a&#8217;, a)<br />
save(&#8216;b&#8217;, b)<br />
结果将分别包含相同部分：<br />
a = {}<br />
a[1] = {}<br />
a[1][1] = &#8220;one&#8221;<br />
a[1][2] = &#8220;two&#8221;<br />
a[2] = 3<br />
b = {}<br />
b["k"] = {}<br />
b["k"][1] = &#8220;one&#8221;<br />
b["k"][2] = &#8220;two&#8221;<br />
然而如果我们使用同一个saved表来调用save函数：<br />
local t = {}<br />
save(&#8216;a&#8217;, a, t)<br />
save(&#8216;b&#8217;, b, t)<br />
结果将共享相同部分：<br />
a = {}<br />
a[1] = {}<br />
a[1][1] = &#8220;one&#8221;<br />
a[1][2] = &#8220;two&#8221;<br />
a[2] = 3<br />
b = {}<br />
b["k"] = a[1]<br />
上面这种方法是Lua中常用的方法，当然也有其他一些方法可以解决问题。比如，我们可以不使用全局变量名来保存(chunk构造一个local值然后返回他)；通过构造一张表，每张表名与其对应的函数对应起来等。Lua给予你权力，由你决定如何实现。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/265/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》11. Data Structures</title>
		<link>http://www.lifangjin.com/archives/264</link>
		<comments>http://www.lifangjin.com/archives/264#comments</comments>
		<pubDate>Mon, 19 Feb 2007 12:44:33 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=264</guid>
		<description><![CDATA[table是Lua中唯一的数据结构,其他语言所提供的其他数据结构比如：arrays, records, lists, queues, sets等,Lua都是通过table来实现,并且在lua中table很好的实现了这些数据结构. 在传统的C语言或者Pascal语言中我们经常使用arrays 和lists（record+pointer）来实现大部分的数据结构,在Lua中不仅可以用table完成同样的功能,而且table的功能更加强大.通过使用table很多算法的实现都简化了,比如你在lua中很少需要自己去实现一个搜索算法,因为table本身就提供了这样的功能. 我们需要花一些时间去学习如何有效的使用table,下面我们通过一些例子来看看如果通过table来实现一些常用的数据结构.首先,我们从arrays 和lists开始, 不仅因为它是其他数据结构的基础,而且是我们所熟悉的.在第一部分语言的介绍中,我们已经接触到了一些相关的内容,在这一章我们将再来完整的学习他。 11.1 数组 在lua中通过整数下标访问表中的元素即可简单的实现数组.并且数组不必事先指定大小,大小可以随需要动态的增长. 通常我们初始化数组的时候就间接的定义了数组的大小,比如下面的代码: a = {} &#8212; new array for i=1, 1000 do a[i] = 0 end 通过初始化,数组a的大小已经确定为1000,企图访问1-1000以外的下标对应的值将返回nil.你可以根据需要定义数组的下标从0,1或者任意其他的数值开始,比如: &#8211; creates an array with indices from -5 to 5 a = {} for i=-5, 5 do a[i] = 0 end 然而在Lua中习惯上数组的下表从1开始,Lua的标准库与此习惯保持一致,因此如果你的数组下标也是从1开始你就可以直接使用标准库的函数,否则就无法直接使用. 我们可以用构造器在创建数组的同时并初始化数组: squares = {1, [...]]]></description>
			<content:encoded><![CDATA[<p>table是Lua中唯一的数据结构,其他语言所提供的其他数据结构比如：arrays, records, lists, queues, sets等,Lua都是通过table来实现,并且在lua中table很好的实现了这些数据结构.<br />
在传统的C语言或者Pascal语言中我们经常使用arrays 和lists（record+pointer）来实现大部分的数据结构,在Lua中不仅可以用table完成同样的功能,而且table的功能更加强大.通过使用table很多算法的实现都简化了,比如你在lua中很少需要自己去实现一个搜索算法,因为table本身就提供了这样的功能.<br />
我们需要花一些时间去学习如何有效的使用table,下面我们通过一些例子来看看如果通过table来实现一些常用的数据结构.首先,我们从arrays 和lists开始, 不仅因为它是其他数据结构的基础,而且是我们所熟悉的.在第一部分语言的介绍中,我们已经接触到了一些相关的内容,在这一章我们将再来完整的学习他。<br />
<span id="more-264"></span><br />
11.1 数组<br />
在lua中通过整数下标访问表中的元素即可简单的实现数组.并且数组不必事先指定大小,大小可以随需要动态的增长.<br />
通常我们初始化数组的时候就间接的定义了数组的大小,比如下面的代码:<br />
a = {} &#8212; new array<br />
for i=1, 1000 do<br />
a[i] = 0<br />
end<br />
通过初始化,数组a的大小已经确定为1000,企图访问1-1000以外的下标对应的值将返回nil.你可以根据需要定义数组的下标从0,1或者任意其他的数值开始,比如:<br />
&#8211; creates an array with indices from -5 to 5<br />
a = {}<br />
for i=-5, 5 do<br />
a[i] = 0<br />
end<br />
然而在Lua中习惯上数组的下表从1开始,Lua的标准库与此习惯保持一致,因此如果你的数组下标也是从1开始你就可以直接使用标准库的函数,否则就无法直接使用.<br />
我们可以用构造器在创建数组的同时并初始化数组:<br />
squares = {1, 4, 9, 16, 25, 36, 49, 64, 81}<br />
这样的语句中数组的大小可以任意的大,甚至几百万.<br />
11.2 矩阵和多维数组<br />
Lua中主要有两种表示矩阵的方法,第一种是用数组的数组表示.也就是说一个表的元素是另一个表.例如,可以使用下面代码创建一个n行m列的矩阵:<br />
mt = {} &#8212; create the matrix<br />
for i=1,N do<br />
mt[i] = {} &#8212; create a new row<br />
for j=1,M do<br />
mt[i][j] = 0<br />
end<br />
end<br />
由于Lua中table是个对象,所以对于每一行我们必须显式的创建一个table,这看起来比起c或者pascal显得冗余,另一方面它也提供了更多的灵活性,例如可以修改前面的例子来创建一个三角矩阵:<br />
for j=1,M do<br />
改成<br />
for j=1,i do<br />
这样实现的三角矩阵比起整个矩阵,仅仅使用一半的内存空间 .<br />
第二中表示矩阵的方法是将行和列组合起来，如果索引下标都是整数,通过第一个索引乘于一个常量(列)再加上第二个索引,看下面的例子实现创建n行m列的矩阵:<br />
mt = {} &#8212; create the matrix<br />
for i=1,N do<br />
for j=1,M do<br />
mt[i*M + j] = 0<br />
end<br />
end<br />
如果索引都是字符串的话,可以用一个单字符将两个字符串索引连接起来构成一个单一的索引下标,例如一个矩阵m,索引下标为s和t,假定s和t都不包含冒号,代码为: m[s..':'..t],如果s或者t包含冒号将导致混淆,比如(&#8220;a:&#8221;, &#8220;b&#8221;) 和(&#8220;a&#8221;, &#8220;:b&#8221;),当对这种情况有疑问的时候可以使用控制字符来连接两个索引字符串,比如’\0’.<br />
实际应用中常常使用稀疏矩阵，稀疏矩阵指矩阵的大部分元素都为空或者0的矩阵。例如，我们通过图的邻接矩阵来存储图，也就是说：当m,n两个节点有连接时，矩阵的m,n值为对应的x，否则为nil。如果一个图有10000个节点，平均每个节点大约有5条边，为了存储这个图需要一个行列分别为10000的矩阵，总计10000*10000个元素，实际上大约只有50000个元素非空(每行有五列非空，与每个节点有五条边对应)。很多数据结构的书上讨论采用何种方式才能节省空间，但是在Lua中你不需要这些技术，因为用table实现的数据本身天生的就具有稀疏的特性。如果用我们上面说的第一种多维数组来表示，需要10000个table，每个table大约需要五个元素(table)；如果用第二种表示方法来表示，只需要一张大约50000个元素的表，不管用那种方式，你只需要存储那些非nil的元素。<br />
11.3链表<br />
Lua中用tables很容易实现链表，每一个节点是一个table，指针是这个表的一个域，并且指向另一个节点(table)。例如，要实现一个只有两个域：值和指针的基本链表，代码如下：<br />
根节点：<br />
list = nil<br />
在链表开头插入一个值为v的节点：<br />
list = {next = list, value = v}<br />
要遍历这个链表只需要：<br />
local l = list<br />
while l do<br />
print(l.value)<br />
l = l.next<br />
end<br />
其他类型的链表，像双向链表和循环链表类似的也是很容易实现的。然后在Lua中在很少情况下才需要这些数据结构，因为通常情况下有更简单的方式来替换链表。比如，我们可以用一个非常大的数组来表示栈，其中一个域n指向栈顶。<br />
11.4队列和双端队列<br />
虽然可以使用Lua的table库提供的insert和remove操作来实现队列，但这种方式实现的队列针对大数据量时效率太低，有效的方式是使用两个索引下标，一个表示第一个元素，另一个表示最后一个元素。<br />
function ListNew ()<br />
return {first = 0, last = -1}<br />
end<br />
为了避免污染全局命名空间，我们重写上面的代码，将其放在一个名为list的table中：<br />
List = {}<br />
function List.new ()<br />
return {first = 0, last = -1}<br />
end<br />
下面，我们可以在常量时间内，完成在队列的两端进行插入和删除操作了。<br />
function List.pushleft (list, value)<br />
local first = list.first &#8211; 1<br />
list.first = first<br />
list[first] = value<br />
end</p>
<p>function List.pushright (list, value)<br />
local last = list.last + 1<br />
list.last = last<br />
list[last] = value<br />
end</p>
<p>function List.popleft (list)<br />
local first = list.first<br />
if first &gt; list.last then error(&#8220;list is empty&#8221;) end<br />
local value = list[first]<br />
list[first] = nil &#8212; to allow garbage collection<br />
list.first = first + 1<br />
return value<br />
end</p>
<p>function List.popright (list)<br />
local last = list.last<br />
if list.first &gt; last then error(&#8220;list is empty&#8221;) end<br />
local value = list[last]<br />
list[last] = nil &#8212; to allow garbage collection<br />
list.last = last &#8211; 1<br />
return value<br />
end<br />
对严格意义上的队列来讲，我们只能调用pushright和 popleft，这样以来，first和last的索引值都随之增加，幸运的是我们使用的是Lua的table实现的，你可以访问数组的元素，通过使用下标从1到20，也可以16,777,216 到 16,777,236。另外，Lua使用双精度表示数字，假定你每秒钟执行100万次插入操作，在数值溢出以前你的程序可以运行200年。<br />
11.5集合和包<br />
假定你想列出在一段源代码中出现的所有标示符，某种程度上，你需要过滤掉那些语言本身的保留字。一些C程序员喜欢用一个字符串数组来表示，将所有的保留字放在数组中，对每一个标示符到这个数组中查找看是否为保留字，有时候为了提高查询效率，对数组存储的时候使用二分查找或者hash算法。<br />
Lua中表示这个集合有一个简单有效的方法，将所有集合中的元素作为下标存放在一个table里，下面不需要查找table，只需要测试看对于给定的元素，表的对应下标的元素值是否为nil。比如：<br />
reserved = {<br />
["while"] = true, ["end"] = true,<br />
["function"] = true, ["local"] = true,<br />
}</p>
<p>for w in allwords() do<br />
if reserved[w] then<br />
&#8211; `w&#8217; is a reserved word<br />
&#8230;<br />
还可以使用辅助函数更加清晰的构造集合：<br />
function Set (list)<br />
local set = {}<br />
for _, l in ipairs(list) do set[l] = true end<br />
return set<br />
end</p>
<p>reserved = Set{&#8220;while&#8221;, &#8220;end&#8221;, &#8220;function&#8221;, &#8220;local&#8221;, }<br />
11.6字符串缓冲<br />
假定你要拼接很多个小的字符串为一个大的字符串，比如，从一个文件中逐行读入字符串。你可能写出下面这样的代码：<br />
&#8211; WARNING: bad code ahead!!<br />
local buff = &#8220;&#8221;<br />
for line in io.lines() do<br />
buff = buff .. line .. &#8220;\n&#8221;<br />
end<br />
尽管这段代码看上去很正常，但在Lua中他的效率极低，在处理大文件的时候，你会明显看到很慢，例如，需要花大概1分钟读取350KB的文件。(这就是为什么Lua专门提供了io.read(*all)选项，她读取同样的文件只需要0.02s)<br />
为什么这样呢？Lua使用真正的垃圾收集算法，但他发现程序使用太多的内存他就会遍历他所有的数据结构去释放垃圾数据，一般情况下，这个算法有很好的性能(Lua的快并非偶然的)，但是上面那段代码loop使得算法的效率极其低下。<br />
为了理解现象的本质，假定我们身在loop中间，buff已经是一个50KB的字符串，每一行的大小为20bytes，当Lua执行buff..line..&#8221;\n&#8221;时，她创建了一个新的字符串大小为50,020 bytes，并且从buff中将50KB的字符串拷贝到新串中。也就是说，对于每一行，都要移动50KB的内存，并且越来越多。读取100行的时候(仅仅2KB)，Lua已经移动了5MB的内存，使情况变遭的是下面的赋值语句：<br />
buff = buff .. line .. &#8220;\n&#8221;<br />
老的字符串变成了垃圾数据，两轮循环之后，将有两个老串包含超过100KB的垃圾数据。这个时候Lua会做出正确的决定，进行他的垃圾收集并释放100KB的内存。问题在于每两次循环Lua就要进行一次垃圾收集，读取整个文件需要进行200次垃圾收集。并且它的内存使用是整个文件大小的三倍。<br />
这个问题并不是Lua特有的：其它的采用垃圾收集算法的并且字符串不可变的语言也都存在这个问题。Java是最著名的例子，Java专门提供StringBuffer来改善这种情况。<br />
在继续进行之前，我们应该做个注释的是，在一般情况下，这个问题并不存在。对于小字符串，上面的那个循环没有任何问题。为了读取整个文件我们可以使用io.read(*all)，可以很快的将这个文件读入内存。但是在某些时候，没有解决问题的简单的办法，所以下面我们将介绍更加高效的算法来解决这个问题。<br />
我们最初的算法通过将循环每一行的字符串连接到老串上来解决问题，新的算法避免如此：它连接两个小串成为一个稍微大的串，然后连接稍微大的串成更大的串。。。算法的核心是：用一个栈，在栈的底部用来保存已经生成的大的字符串，而小的串从栈定入栈。栈的状态变化和经典的汉诺塔问题类似：位于栈下面的串肯定比上面的长，只要一个较长的串入栈后比它下面的串长，就将两个串合并成一个新的更大的串，新生成的串继续与相邻的串比较如果长于底部的将继续进行合并，循环进行到没有串可以合并或者到达栈底。<br />
function newStack ()<br />
return {&#8220;&#8221;} &#8212; starts with an empty string<br />
end</p>
<p>function addString (stack, s)<br />
table.insert(stack, s) &#8212; push &#8216;s&#8217; into the the stack<br />
for i=table.getn(stack)-1, 1, -1 do<br />
if string.len(stack[i]) &gt; string.len(stack[i+1]) then<br />
break<br />
end<br />
stack[i] = stack[i] .. table.remove(stack)<br />
end<br />
end<br />
要想获取最终的字符串，我们只需要从上向下一次合并所有的字符串即可。table.concat函数可以将一个列表的所有串合并。<br />
使用这个新的数据结构，我们重写我们的代码：<br />
local s = newStack()<br />
for line in io.lines() do<br />
addString(s, line .. &#8220;\n&#8221;)<br />
end<br />
s = toString(s)<br />
最终的程序读取350 KB的文件只需要0.5s，当然调用io.read(&#8220;*all&#8221;)仍然是最快的只需要0.02s。</p>
<p>实际上，我们调用io.read(&#8220;*all&#8221;)的时候，io.read就是使用我们上面的数据结构，只不过是用C实现的，在Lua标准库中，有些其他函数也是用C实现的，比如table.concat，使用table.concat我们可以很容易的将一个table的中的字符串连接起来，因为它使用C实现的，所以即使字符串很大它处理起来速度还是很快的。<br />
Concat接受第二个可选的参数，代表插入的字符串之间的分隔符。通过使用这个参数，我们不需要在每一行之后插入一个新行：<br />
local t = {}<br />
for line in io.lines() do<br />
table.insert(t, line)<br />
end<br />
s = table.concat(t, &#8220;\n&#8221;) .. &#8220;\n&#8221;<br />
io.lines迭代子返回不带换行符的一行，concat在字符串之间插入分隔符，但是最后一字符串之后不会插入分隔符，因此我们需要在最后加上一个分隔符。最后一个连接操作复制了整个字符串，这个时候整个字符串可能是很大的。我们可以使用一点小技巧，插入一个空串：<br />
table.insert(t, &#8220;&#8221;)<br />
s = table.concat(t, &#8220;\n&#8221;)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/264/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Programming in Lua中文版》10.Complete Examples</title>
		<link>http://www.lifangjin.com/archives/263</link>
		<comments>http://www.lifangjin.com/archives/263#comments</comments>
		<pubDate>Mon, 19 Feb 2007 12:42:30 +0000</pubDate>
		<dc:creator>李 方进</dc:creator>
				<category><![CDATA[开发编程]]></category>
		<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">http://www.lifangjin.com/?p=263</guid>
		<description><![CDATA[我们看两个完整的例子来阐明Lua语言的使用.第一个例子来自于Lua网站,他展示了Lua作为数据描述语言的使用.第二个例子讲解了马尔可夫链算法的实现,这个算法在Kernighan &#38; Pike著作的Practice of Programming书中也有描述.这两个完整的例子之后颐墙崾鳯ua语言方面的介绍,后面将继续介绍table和面向对象的内容以及标准库,C-API等. 10.1 Lua作为数据描述语言使用 Lua网站保留一个包含世界各地使用Lua创建的工程的例子的数据库.在数据库中我们用一个构造器以自动归档的方式表示每一个工程入口点,代码如下： entry{ title = &#8220;Tecgraf&#8221;, org = &#8220;Computer Graphics Technology Group, PUC-Rio&#8221;, url = &#8220;http://www.tecgraf.puc-rio.br/&#8221;, contact = &#8220;Waldemar Celes&#8221;, description = [[ TeCGraf is the result of a partnership between PUC-Rio, the Pontifical Catholic University of Rio de Janeiro, and PETROBRAS, the Brazilian Oil Company. TeCGraf is Lua's [...]]]></description>
			<content:encoded><![CDATA[<p>我们看两个完整的例子来阐明Lua语言的使用.第一个例子来自于Lua网站,他展示了Lua作为数据描述语言的使用.第二个例子讲解了马尔可夫链算法的实现,这个算法在Kernighan &amp; Pike著作的Practice of Programming书中也有描述.这两个完整的例子之后颐墙崾鳯ua语言方面的介绍,后面将继续介绍table和面向对象的内容以及标准库,C-API等.<br />
<span id="more-263"></span><br />
10.1 Lua作为数据描述语言使用<br />
Lua网站保留一个包含世界各地使用Lua创建的工程的例子的数据库.在数据库中我们用一个构造器以自动归档的方式表示每一个工程入口点,代码如下：<br />
entry{<br />
title = &#8220;Tecgraf&#8221;,<br />
org = &#8220;Computer Graphics Technology Group, PUC-Rio&#8221;,<br />
url = &#8220;http://www.tecgraf.puc-rio.br/&#8221;,<br />
contact = &#8220;Waldemar Celes&#8221;,<br />
description = [[<br />
TeCGraf is the result of a partnership between PUC-Rio,<br />
the Pontifical Catholic University of Rio de Janeiro,<br />
and <a href="http://www.petrobras.com.br/">PETROBRAS</a>,<br />
the Brazilian Oil Company.<br />
TeCGraf is <a href="http://www.lifangjin.com/archives/tag/lua" class="st_tag internal_tag" rel="tag" title="Posts tagged with Lua">Lua</a>'s birthplace,<br />
and the language has been used there since 1993.<br />
Currently, more than thirty programmers in TeCGraf use<br />
Lua regularly; they have written more than two hundred<br />
thousand lines of code, distributed among dozens of<br />
final products.]]<br />
}<br />
有趣的是,工程入口的列表是存放在一个Lua文件中的,每个工程入口以table的形式作为参数去调用entry函数.我们的目的是写一个程序将这些数据以html格式展示出来.由于工程太多,我们首先列出工程的标题,然后显示每个工程的明细.结果如下:</p>
<p>Here are brief descriptions of some projects around the<br />
world that use <a href="home.html">Lua</a>.</p>
<ul>
<li><a href="#1">TeCGraf</a></li>
<li>&#8230;</li>
</ul>
<h3><a name="1" href="http://www.tecgraf.puc-rio.br/" title="1">TeCGraf</a><small><em>Computer Graphics Technology Group,<br />
PUC-Rio</em></small></h3>
<p>TeCGraf is the result of a partnership between<br />
&#8230;<br />
distributed among dozens of final products.</p>
<p>Contact: Waldemar Celes</p>
<p><a name="2" title="2"></a></p>
<hr />&#8230;<br />
为了读取数据,我们需要做的是正确的定义函数entry,然后使用dofile直接运行数据文件即可(db.lua).注意,我们需要遍历入口列表两次,第一次为了获取标题,第二次为了获取每个工程的表述.一种方法是:使用相同的entry函数运行数据文件一次将所有的入口放在一个数组内;另一种方法:使用不同的entry函数运行数据文件两次.因为Lua编译文件是很快的,这里我们选用第二种方法.<br />
首先,我们定义一个辅助函数用来格式化文本的输出(参见5.2函数部分内容)<br />
function fwrite (fmt, &#8230;)<br />
return io.write(string.format(fmt, unpack(arg)))<br />
end<br />
第二,我们定义一个BEGIN函数用来写html页面的头部<br />
function BEGIN()<br />
io.write([[</p>
<p>Here are brief descriptions of some projects around the<br />
world that use <a href="home.html">Lua</a>.</p>
<p>]])<br />
end<br />
第三,定义entry函数<br />
a.第一个entry函数,将每个工程一列表方式写出,entry的参数o是描述工程的table.<br />
function entry0 (o)<br />
N=N + 1<br />
local title = o.title or &#8216;(no title)&#8217;<br />
fwrite(&#8216;</p>
<li><a href="#%d">%s</a>\n&#8217;, N, title)<br />
end<br />
如果o.title为 nil表明table中的域title没有提供,我们用固定的&#8221;no title&#8221;替换.<br />
b.第二个entry函数,写出工程所有的相关信息,稍微有些复杂,因为所有项都是可选的.<br />
function entry1 (o)<br />
N=N + 1<br />
local title = o.title or o.org or &#8216;org&#8217;<br />
fwrite(&#8216;</p>
<hr />\n</p>
<h3>\n&#8217;)<br />
local href = &#8221;if o.url then<br />
href = string.format(&#8216; href=&#8221;%s&#8221; mce_href=&#8221;%s&#8221;&#8216;, o.url)<br />
end<br />
fwrite(&#8216;<a name="%d" title="%d"></a>%s\n&#8217;, N, href, title)</p>
<p>if o.title and o.org then<br />
fwrite(&#8216;<br />
\n<small><em>%s</em></small>&#8216;, o.org)<br />
end<br />
fwrite(&#8216;\n</h3>
<p>\n&#8217;)</p>
<p>if o.description then<br />
fwrite(&#8216;%s&#8217;, string.gsub(o.description,<br />
&#8216;\n\n\n*&#8217;, &#8216;</p>
<p>\n&#8217;))<br />
fwrite(&#8216;</p>
<p>\n&#8217;)<br />
end</p>
<p>if o.email then<br />
fwrite(&#8216;Contact: <a href="mailto:%s">%s</a>\n&#8217;,<br />
o.email, o.contact or o.email)<br />
elseif o.contact then<br />
fwrite(&#8216;Contact: %s\n&#8217;, o.contact)<br />
end<br />
end<br />
由于html中使用双引号,为了避免冲突我们这里使用单引号表示串.<br />
第四,定义END函数,写html的尾部<br />
function END()<br />
fwrite(&#8216;\n&#8217;)<br />
end<br />
在主程序中,我们首先使用第一个entry运行数据文件输出工程名称的列表,然后再以第二个entry运行数据文件输出工程相关信息.<br />
BEGIN()</p>
<p>N = 0<br />
entry = entry0<br />
fwrite(&#8216;</p>
<ul>\n&#8217;)<br />
dofile(&#8216;db.lua&#8217;)<br />
fwrite(&#8216;</ul>
<p>\n&#8217;)</p>
<p>N = 0<br />
entry = entry1<br />
dofile(&#8216;db.lua&#8217;)</p>
<p>END()<br />
10.2 马尔可夫链算法<br />
我们第二个例子是马尔可夫链算法的实现,我们的程序以前n(n=2)个单词串为基础随机产生一个文本串.<br />
程序的第一部分读出原文,并且对没两个单词的前缀建立一个表,这个表给出了具有那些前缀的单词的一个顺序.建表完成后,这个程序利用这张表生成一个随机的文本.在此文本中,每个单词都跟随着它的的前两个单词,这两个单词在文本中有相同的概率.这样,我们就产生了一个非常随机,但并不完全随机的文本.例如,当应用&#8230;&#8230;这个程序的输出结果会出现“构造器也可以通过表构造器,那么一下几行的插入语对于整个文件来说，不是来存储每个功能的内容,而是来展示它的结构.”如果你想在队列里找到最大元素并返回最大值,接着显示提示和运行代码.下面的单词是保留单词,不能用在度和弧度之间转换.<br />
我们编写一个函数用来将两个单词中间加上空个连接起来:<br />
function prefix (w1, w2)<br />
return w1 .. &#8216; &#8216; .. w2<br />
end<br />
我们用NOWORD(即\n)表示文件的结尾并且初始化前缀单词,例如,下面的文本:<br />
the more we try the more we do<br />
初始化构造的表为:<br />
{ ["\n \n"] = {&#8220;the&#8221;},<br />
["\n the"] = {&#8220;more&#8221;},<br />
["the more"] = {&#8220;we&#8221;, &#8220;we&#8221;},<br />
["more we"] = {&#8220;try&#8221;, &#8220;do&#8221;},<br />
["we try"] = {&#8220;the&#8221;},<br />
["try the"] = {&#8220;more&#8221;},<br />
["we do"] = {&#8220;\n&#8221;},<br />
}<br />
我们使用全局变量statetab来保存这个表,下面我们完成一个插入函数用来在这个statetab中插入新的单词.<br />
function insert (index, value)<br />
if not statetab[index] then<br />
statetab[index] = {value}<br />
else<br />
table.insert(statetab[index], value)<br />
end<br />
end<br />
这个函数中首先检查指定的前缀是否存在,如果不存在则创建一个新的并赋上新值.如果已经存在则调用table.insert将新值插入到列表尾部.<br />
我们使用两个变量w1和w2来保存最后读入的两个单词的值,对于每一个前缀,我们保存紧跟其后的单词的列表.例如上面例子中初始化构造的表.<br />
初始化表之后,下面来看看如何生成一个MAXGEN(=1000)个单词的文本.首先,重新初始化w1和w2,然后对于每一个前缀,在其next单词的列表中随机选择一个,打印此单词并更新w1和w2,完整的代码如下:<br />
&#8211; Markov Chain Program in Lua</p>
<p>function allwords ()<br />
local line = io.read() &#8212; current line<br />
local pos = 1 &#8212; current position in the line<br />
return function () &#8212; iterator function<br />
while line do &#8212; repeat while there are lines<br />
local s, e = string.find(line, &#8220;%w+&#8221;, pos)<br />
if s then &#8212; found a word?<br />
pos = e + 1 &#8212; update next position<br />
return string.sub(line, s, e) &#8212; return the word<br />
else<br />
line = io.read() &#8212; word not found; try next line<br />
pos = 1 &#8212; restart from first position<br />
end<br />
end<br />
return nil &#8212; no more lines: end of traversal<br />
end<br />
end</p>
<p>function prefix (w1, w2)<br />
return w1 .. &#8216; &#8216; .. w2<br />
end</p>
<p>local statetab</p>
<p>function insert (index, value)<br />
if not statetab[index] then<br />
statetab[index] = {n=0}<br />
end<br />
table.insert(statetab[index], value)<br />
end</p>
<p>local N = 2<br />
local MAXGEN = 10000<br />
local NOWORD = &#8220;\n&#8221;</p>
<p>&#8211; build table<br />
statetab = {}<br />
local w1, w2 = NOWORD, NOWORD<br />
for w in allwords() do<br />
insert(prefix(w1, w2), w)<br />
w1 = w2; w2 = w;<br />
end<br />
insert(prefix(w1, w2), NOWORD)</p>
<p>&#8211; generate text<br />
w1 = NOWORD; w2 = NOWORD &#8212; reinitialize<br />
for i=1,MAXGEN do<br />
local list = statetab[prefix(w1, w2)]<br />
&#8211; choose a random item from list<br />
local r = math.random(table.getn(list))<br />
local nextword = list[r]<br />
if nextword == NOWORD then return end<br />
io.write(nextword, &#8221; &#8220;)<br />
w1 = w2; w2 = nextword<br />
end</li>
]]></content:encoded>
			<wfw:commentRss>http://www.lifangjin.com/archives/263/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

