【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3749|回复: 8

Lua用户手册

[复制链接]
发表于 2007-5-10 11:34:26 | 显示全部楼层 |阅读模式

http://www.e171.com/bbs/thread.php?fid-40-search--page-3.html

1.1    Hello world 程序


       print("Hello World")


       假定你把上面这句保存在hello.lua文件中,你在命令行只需要:


       prompt> lua hello.lua


看到结果了吗?


让我们来看一个稍微复杂点的例子:


-- defines a factorial function


    function fact (n)


      if n == 0 then


        return 1


      else


        return n * fact(n-1)


      end


    end


   


    print("enter a number:")


    a = io.read("*number")        -- read a number


    print(fact(a))


这个例子定义了一个函数,计算输入参数n的阶乘;本例要求用户输入一个数字n,然后打印n的阶乘。


1.2    Chunks


       Chunk是一系列语句,Lua执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个Chunk.


       每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用;分开


              a = 1   b = a*2    -- ugly, but valid


       一个Chunk可以是一个语句,也可以是一系列语句的组合,还可以是函数,Chunk可以很大,在Lua中几个MByte的Chunk是很常见的。


       你还可以以交互模式运行Lua,不带参数运行Lua:


       Lua 5.0  Copyright © 1994-2003 Tecgraf, PUC-Rio


    >


你键入的每个命令(比如:"Hello World")在你键入回车之后立即被执行,键入文件结束符可以退出交互模式(ctrl-D in Unix, ctrl-Z in DOS/Windows),或者调用OS库的os.exit()函数也可以退出.


       在交互模式下,Lua通常把每一个行当作一个chunk,但如果Lua一行不是一个完整的chunk时,他会等待继续输入直到得到一个完整的chunk.在Lua等待续行时,显示不同的提示符(一般是>>).


       可以通过指定参数让Lua执行一系列chunk.例如:假定一个文件a内有单个语句x=1;另一个文件b有语句 print(x)


       prompt> lua -la -lb


命令首先在一个chunk内先运行a然后运行b.(注意:-l选项会调用require,将会在指定的目录下搜索文件,如果环境变量没有设好,上面的命令可能不能正确运行.我们将在8.1节详细更详细的讨论the require function)


       -i选项要求Lua运行指定chunk后进入交互模式.


       prompt> lua -i -la -lb


将在一个chunk内先运行a然后运行b, 最后直接进入交互模式.


       另一个连接外部chunk的方式是使用dofile函数,dofile函数加载文件并执行它.假设有一个文件:


       -- file 'lib1.lua'


   


    function norm (x, y)


      local n2 = x^2 + y^2


      return math.sqrt(n2)


    end


   


    function twice (x)


      return 2*x


    end


在交互模式下:


       > dofile("lib1.lua")   -- load your library


    > n = norm(3.4, 1.0)


    > print(twice(n))      --> 7.0880180586677


-i和dofile在调试或者测试Lua代码时是很方便的.


1.2    全局变量


全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil.


       print(b)  --> nil


    b = 10


    print(b)  --> 10


如果你想删除一个全局变量,只需要将变量负值为nil


       b = nil


    print(b)  --> nil


这样变量b就好像从没被使用过一样.换句话说, 当且仅当一个变量不等于nil时,这个变量存在。


1.3   词法约定


       标示符:字母(letter)或者下划线开头的字母、下划线、数字序列.最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的.Lua中,letter的含义是依赖于本地环境的.


       保留字:不能当作标示符


以下字符为Lua的保留字,不能当作标识符。


       and       break     do        else      elseif


    end       false     for       function  if


    in        local     nil       not       or


    repeat    return    then      true      until


    while


注意:Lua是大小写敏感的.


    注释:单行注释:--


    多行注释:--[[ --]]


    --[[


    print(10)         -- no action (comment)


    --]]


1.4    命令行方式


lua [options] [script [args]]


       -e:直接将命令传入Lua


              prompt> lua -e "print(math.sin(12))"   --> -0.53657291800043


       -l:加载一个文件.


       -i:进入交互模式.


       _PROMPT内置变量作为交互模式的提示符


              prompt> lua -i -e "_PROMPT=' lua> '"


          lua>


Lua的运行过程,在运行参数之前,Lua会查找环境变量LUA_INIT的值,如果变量存在并且值为@filename,Lua将加载指定文件.如果变量存在但不是以@开头,Lua假定filename为Lua代码文件并且运行他.利用这个特性,我们可以通过配置,灵活的设置交互模式的环境.可以加载包,修改提示符和路径,定义自己的函数,修改或者重名名函数等.


       全局变量arg存放Lua的命令行参数.


       prompt> lua script a b c


在运行以前,Lua使用所有参数构造arg表.脚本名索引为0,脚本的参数从1开始增加.脚本前面的参数从-1开始减少.


       prompt> lua -e "sin=math.sin" script a b


arg表如下:


    arg[-3] = "lua"


    arg[-2] = "-e"


    arg[-1] = "sin=math.sin"


    arg[0] = "script"


    arg[1] = "a"


    arg[2] = "b"

 楼主| 发表于 2007-5-10 11:35:35 | 显示全部楼层

Lua用户手册(2)-类型和值
Lua是动态类型语言,变量不要类型定义.Lua中有8个基本类型分别为:nil, boolean, number, string, userdata, function, thread, and table. 函数type可以测试给定变量或者值的类型。


       print(type("Hello world"))  --> string


    print(type(10.4*3))         --> number


    print(type(print))          --> function


    print(type(type))           --> function


    print(type(true))           --> boolean


    print(type(nil))            --> nil


    print(type(type(X)))        --> string


变量没有预定义的类型,每一个变量都可能包含任一种类型的值.


       print(type(a))   --> nil   (`a' is not initialized)


    a = 10


    print(type(a))   --> number


    a = "a string!!"


    print(type(a))   --> string


    a = print        -- yes, this is valid!


    a(type(a))       --> function


注意上面最后两行,我们可以使用function像使用其他值一样使用(更多的介绍参考第六章).一般情况下同一变量代表不同类型的值会造成混乱,最好不要用,但是特殊情况下可以带来便利,比如nil.


2.1    nil:


Lua中特殊的类型,他只有一个值:nil;一个全局变量没有被赋值以前默认值为nil;给全局变量负nil可以删除该变量。


2.2    booleans:


两个取值false和true.但要注意Lua中所有的值都可以作为条件.在控制结构的条件中除了false和nil为假,其他值都为真.所以Lua认为0和空串都是真.


2.3    numbers:


表示实数,Lua中没有整数.一般有个错误的看法CPU运算浮点数比整数慢.事实不是如此,用实数代替整数不会有什么误差(除非数字大于100,000,000,000,000).Lua的numbers可以处理任何长整数不用担心误差.你也可以在编译Lua的时候使用长整型或者单精度浮点型代替numbers,在一些平台硬件不支持浮点数的情况下这个特性是非常有用的,具体的情况请参考Lua发布版所附的详细说明.和其他语言类似,数字常量的小数部分和指数部分都是可选的,数字常量的例子:


        4     0.4     4.57e-3     0.3e12     5e+20


2.4    Strings:


Strings指字符的序列.lua 是8位字节,所以字符串可以包含任何数值字符,包括嵌入的0。 这意味着你可以存储任意的二进制数据在一个字符串里.Lua中字符串是不可以修改的,你可以创建一个新的变量存放你要的字符串,如下:


       a = "one string"


    b = string.gsub(a, "one", "another")  -- change string parts


    print(a)       --> one string


    print(b)       --> another string


string和其他对象一样,Lua自动进行内存分配和释放,一个string可以只包含一个字母也可以包含一本书,Lua可以高效的处理长字符串,1M的string在Lua中是很常见的.


可以使用单引号或者双引号表示字符串


       a = "a line"


    b = 'another line'


    为了风格统一,最好使用一种,除非两种引号嵌套情况.对于字符串中含有引号的情况还可以使用转义符\来表示.Lua中的转义序列有:


    \a bell


       \b back space                             --后退


       \f form feed                        --换页


       \n newline                           --换行


       \r carriage return                  --回车


       \t horizontal tab                   --制表


       \v vertical tab                            


       \\ backslash                         --“\”


       \" double quote                   双引号


       \' single quote                            单引号


       \[ left square bracket            左中括号


       \] right square bracket          右中括号


例子:


       > print("one line\nnext line\n\"in quotes\", 'in quotes'")


    one line


    next line


    "in quotes", 'in quotes'


    > print('a backslash inside quotes: \'\\\'')


    a backslash inside quotes: '\'


    > print("a simpler way: '\\'")


    a simpler way: '\'


还可以在字符串中使用\ddd(ddd为三位十进制数字)方式表示字母.


       "alo\n123\"" 和 '\97lo\10\04923"'是相同的


还可以使用[[...]]表示字符串.这种形式的字符串可以包含多行也,可以嵌套且不会解释转义序列,如果第一个字符是换行符会被自动忽略掉.这种形式的字符串用来包含一段代码是非常方便的.


      page = [[


    <HTML>


    <HEAD>


    <TITLE>An HTML Page</TITLE>


    </HEAD>


    <BODY>


     Lua


     [[a text between double brackets]]


    </BODY>


    </HTML>


    ]]


   


    io.write(page)


运行时,Lua会自动在string和numbers之间自动进行类型转换,当一个字符串使用算术操作符时,string就会被转成数字.


       print("10" + 1)           --> 11


    print("10 + 1")           --> 10 + 1


    print("-5.3e - 10"*"2")   --> -1.06e-09


    print("hello" + 1)        -- ERROR (cannot convert "hello")


反过来,当Lua期望一个string而碰到数字时,会将数字转成string .


       print(10 .. 20)        --> 1020


..在Lua中是字符串连接符,当在一个数字后面写..时必须加上空格以防止被解释错.


尽管字符串和数字可以自动转换,但两者是不同的,像 10 == "10"这样的比较永远都是错的.如果需要显式将string转成数字可以使用函数tonumber(),如果string不是正确的数字该函数将返回nil.


       line = io.read()     -- read a line


    n = tonumber(line)   -- try to convert it to a number


    if n == nil then


      error(line .. " is not a valid number")


    else


      print(n*2)


    end


反之,可以调用tostring()将数字转成字符串,这种转换一直有效:


       print(tostring(10) == "10")   --> true


    print(10 .. "" == "10")       --> true

2.5    tables


Lua的tables实现了关联数组,关联数组指不仅可以通过数字下标检索数据,还可以通过别的类型的值检索数据.Lua中除了nil以外的类型都可以作为tables的索引下标.另外tables没有固定的大小,你可以根据需要动态的调整他的大小.tables是Lua主要的也是唯一的数据结构,我们可以通过他实现传统数组, 符号表, 集合, 记录(pascal), 队列, 以及其他的数据结构.Lua的包也是使用tables来描述的,io.read意味着调用io包中的read函数,对Lua而言意味着使用字符串read作为key访问io表.


Lua中tables不是变量也不是值而是对象.你可以把tables当作自动分配的对象,在程序中只需要操纵表的引用(指针)即可.Lua中不需要声明表,仅使用最简单的{}表达式语句即可创建表.


       a = {}     -- create a table and store its reference in `a'


    k = "x"


    a[k] = 10        -- new entry, with key="x" and value=10


    a[20] = "great"  -- new entry, with key=20 and value="great"


    print(a["x"])    --> 10


    k = 20


    print(a[k])      --> "great"


    a["x"] = a["x"] + 1     -- increments entry "x"


    print(a["x"])    --> 11


表是匿名的,这就意味着表和持有表的变量没有必然的关系.


       a = {}


    a["x"] = 10


    b = a      -- `b' refers to the same table as `a'


    print(b["x"])  --> 10


    b["x"] = 20


    print(a["x"])  --> 20


    a = nil    -- now only `b' still refers to the table


    b = nil    -- now there are no references left to the table


当程序中不再引用表时,这个表将被删除,内存便可以重新被利用.表可以使用不同的索引类型存储值.索引大小随着表中元素个数的增加而增加.


       a = {}     -- empty table


    -- create 1000 new entries


    for i=1,1000 do a = i*2 end


    print(a[9])    --> 18


    a["x"] = 10


    print(a["x"])  --> 10


    print(a["y"])  --> nil


最后一行,表对应的域没有被初始化所以为nil,和全局变量一样,Lua的全局变量存储正是使用表来存储的.


可以使用域名作为索引下表访问表中元素,Lua也支持a.name代替a["name"],所以我们可以用更清晰的方式重写上面的例子:


       a.x = 10                    -- same as a["x"] = 10


    print(a.x)                  -- same as print(a["x"])


    print(a.y)                  -- same as print(a["y"])


两种方式可以混合使用,对于Lua来说,两种方式相同,但对于读者来说单一的风格更易理解.


常见的错误:混淆a.x 和a[x];第一种表示a["x"],即访问域为字符串"x"的表中元素,第二种表示使用变量x作为索引下标访问表中元素.


       a = {}


    x = "y"


    a[x] = 10                 -- put 10 in field "y"


    print(a[x])   --> 10      -- value of field "y"


    print(a.x)    --> nil     -- value of field "x" (undefined)


    print(a.y)    --> 10      -- value of field "y"


只要使用整数作为索引下标就可以表示传统的数组了,不需要指定数组大小:


       -- read 10 lines storing them in a table


    a = {}


    for i=1,10 do


      a = io.read()


    end


当遍历数组元素时,第一个没有初始化的元素返回nil,可以用这个当作数组下标的边界标志.可以用下面的代码打印出上个例子读入的行:


       -- print the lines


    for i,line in ipairs(a) do


      print(line)


    end


既然可以使用任意值作为表的下标,你可以以任何数字作为数组下标的开始,但是Lua中一般以1开始而不是0(c语言).Lua标准库也是以这个设计的.


有一点需要特别注意,否则你的代码中可能会引入很多难以发现的bug.因为我们可以使用任意类型的值作为索引下标,因此要注意:number 0 和string "0"是不同的,同样strings "+1", "01", and "1"也是不同的.


       i = 10; j = "10"; k = "+10"


    a = {}


    a = "one value"


    a[j] = "another value"


    a[k] = "yet another value"


    print(a[j])            --> another value


    print(a[k])            --> yet another value


    print(a[tonumber(j)])  --> one value


    print(a[tonumber(k)])  --> one value


当对你检索的类型有疑问时,请使用显示类型转换.


2.6    Functions


函数是第一类值(和其他变量相同),意味着函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值.这个特性给了语言很大的灵活性:一个程序可以重新定义函数增加新的功能或者为了避免运行不可靠代码创建安全运行环境而隐藏函数,此外这特性在Lua实现面向对象中也起了重要作用(在第16章详细讲述).


Lua可以调用lua或者C实现的函数,Lua所有标准库都是用C实现的.标准库包括string库,table库,I/O库,OS库,算术库,debug库.


2.7 Userdata and Threads


userdata 可以将C数据存放在Lua变量中,userdata 在Lua中除了赋值和相等比较外没有预定义的操作.userdata 用来描述应用程序或者使用C实现的库创建的新类型.例如:用标准I/O库来描述文件.下面在C API章节中我们将详细讨论.


在第九章讨论协同操作的时候,我们介绍线程.


 楼主| 发表于 2007-5-10 11:36:21 | 显示全部楼层

Lua用户手册(3)-表达式
Lua中的表达式包括数字常量,字符串常量,变量,一元和二元运算符,函数调用.还可以是非传统的函数定义和表构造.


3.1    算术运算符


       二元运算符:+ - * / ^  (加减乘除幂)


       一元运算符:-  (负值)


       这些运算符的操作数都是实数.


3.2    关系运算符


       <   >   <=  >=  ==  ~=


       这些操作符返回结果为false或者true;==和~=比较两个值,如果两个值类型不同,Lua认为两者不同;nil只和自己相等.Lua通过引用比较tables,userdata,functions.也就是说当且仅当两者表示同一个对象时相等.


              a = {}; a.x = 1; a.y = 0


           b = {}; b.x = 1; b.y = 0


           c = a


          


           a==c but a~=b


    Lua比较数字按传统的数字大小进行,比较字符串按字母的顺序进行,但是字母顺序依赖于本地环境.


    当比较不同类型的值的时候要特别注意:


           "0"==0 is false


           2<15 is obviously true


           "2"<"15" is false (alphabetical order!).


    为了避免不一致的结果,混合比较数字和字符串,Lua会报错,比如:2<"15


3.3    逻辑运算符


       and or not


       逻辑运算符认为false和nil是假(false),其他为真,0也是true.


       and 和 or的运算结果不是true和false,而是和它的两个操作数相关。


  a and b:如果a为false,则返回a,否则返回b


  a or  b:如果a为true,则返回a,否则返回b


              print(4 and 5)         --> 5


           print(nil and 13)      --> nil


           print(false and 13)    --> false


           print(4 or 5)          --> 4


           print(false or 5)      --> 5


    一个很实用的技巧:如果x为false或者nil则给x赋初始值v


           x = x or v 等价于 if not x then x = v end                             


       and的优先级比or高;


       C语言中的三元运算符a ? b : c 在Lua中可以这样实现a and b) or c


       not的结果一直返回false或者true


              print(not nil)      --> true


           print(not false)    --> true


           print(not 0)        --> false


           print(not not nil)  --> false


3.4    连接运算符


       ..字符串连接,如果操作数为数字,Lua将数字转成字符串.


              print("Hello " .. "World")  --> Hello World


           print(0 .. 1)               --> 01


3.5    优先级


       从高到低的顺序:


              ^


        not  - (unary)


        *   /


        +   -


        ..


        <   >   <=  >=  ~=  ==


        and


        or


       除了^和..外所有的二元运算符都是左连接的.


              a+i < b/2+1          <-->       (a+i) < ((b/2)+1)


           5+x^2*8              <-->       5+((x^2)*8)


           a < y and y <= z     <-->       (a < y) and (y <= z)


           -x^2                 <-->       -(x^2)


           x^y^z                <-->       x^(y^z)


3.6    表的构造


       构造器是创建和初始化表的表达式.表是Lua特有的功能强大的东西.最简单的构造函数是{},用来创建一个空表.可以直接初始化数组:


       days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}


       Lua将用string "Sunday"初始化days[1](第一个元素索引为1),用"Monday"初始化days[2]...


              print(days[4])  --> Wednesday


       构造函数可以使用任何表达式初始化:


              tab = {sin(1), sin(2), sin(3), sin(4),


             sin(5), sin(6), sin(7), sin(8)}


       如果想初始化一个表作为record使用可以这样:


              a = {x=0, y=0}      和 a = {}; a.x=0; a.y=0等价


       不管用何种方式创建table,我们都可以向表中添加或者删除任何类型的域,构造函数仅仅影响表的初始化.


              w = {x=0, y=0, label="console"}


           x = {sin(0), sin(1), sin(2)}


           w[1] = "another field"


           x.f = w


           print(w["x"])   --> 0


           print(w[1])     --> another field


           print(x.f[1])   --> another field


           w.x = nil       -- remove field "x"


       每次调用构造函数,Lua都会创建一个新的table,可以使用table构造一个list:


              list = nil


           for line in io.lines() do


             list = {next=list, value=line}


           end


       这段代码从标准输入读进每行,然后反序形成链表.下面的代码打印链表的内容:


              l = list


           while l do


             print(l.value)


             l = l.next


           end


       在同一个构造函数中可以混合列表风格和record风格进行初始化,如:


              polyline = {color="blue", thickness=2, npoints=4,


                 {x=0,   y=0},


                 {x=-10, y=0},


                 {x=-10, y=1},


                 {x=0,   y=1}


               }


       这个例子也表明我们可以嵌套构造函数来表示复杂的数据结构.


              print(polyline[2].x)    --> -10


       上面两种构造函数的初始化方式还有限制,比如你不能使用负索引初始化一个表中元素,字符串索引也不能被恰当的表示,下面介绍一种更一般的初始化方式,我们用[expression]显示的表示将被初始化的索引:


              opnames = {["+"] = "add", ["-"] = "sub",


                      ["*"] = "mul", ["/"] = "div"}


          


           i = 20; s = "-"


           a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s}


          


           print(opnames)    --> sub


           print(a[22])         --> ---


       list风格初始化和record风格初始化是这种一般初始化的特例:


              {x=0, y=0}                                 <-->    {["x"]=0, ["y"]=0}


           {"red", "green", "blue"}        <-->    {[1]="red", [2]="green", [3]="blue"}


       如果真的想要数组下标从0开始:


              days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday",


                   "Thursday", "Friday", "Saturday"}


       注意:不推荐数组下标从0开始,否则很多标准库不能使用.


       在构造函数的最后的","是可选的,可以方便以后的扩展.


              a = {[1]="red", [2]="green", [3]="blue",}


       在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素.


              {x=10, y=45; "one", "two", "three"}


 楼主| 发表于 2007-5-10 11:38:56 | 显示全部楼层

Lua用户手册(4)-语句
Lua像C和PASCAL几乎支持所有的传统语句:赋值语句,控制结构语句,函数调用等,同时也支持非传统的多变量赋值,局部变量声明.


4.1    赋值语句


       赋值是改变一个变量的值和改变表域的最基本的方法.


              a = "hello" .. "world"


           t.n = t.n + 1


       Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量.         a, b = 10, 2*x


       表示,a=10 并且b=2*x


       遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:


              x, y = y, x                -- swap 'x' for 'y'


           a, a[j] = a[j], a    -- swap 'a' for 'a'


       当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略,:


       a.变量个数>值的个数          按变量个数补足nil


       b.变量个数<值的个数         多余的值会被忽略


              a, b, c = 0, 1                                      


        print(a,b,c)           --> 0   1   nil          


        a, b = a+1, b+1, b+2   -- value of b+2 is ignored


        print(a,b)             --> 1   2                


        a, b, c = 0                                     


        print(a,b,c)           --> 0   nil   nil       


    上面最后一个例子是一个常见的错误情况,注意:如果要对多个变量赋值必须依次对每个变量赋值 .


           a, b, c = 0, 0, 0


           print(a,b,c)           --> 0   0   0


       多值赋值经常用来交换变量或者将函数调用返回给变量:


              a, b = f()


       f()返回两个值,第一个赋给a,第二个赋给b.


4.2    局部变量与代码块(block)


       使用local创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效.代码块:指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串).


              x = 10


           local i = 1        -- local to the chunk


          


           while i<=x do


             local x = i*2    -- local to the while body


             print(x)         --> 2, 4, 6, 8, ...


             i = i + 1


           end


          


           if i > 20 then


             local x          -- local to the "then" body


             x = 20


             print(x + 2)


           else


             print(x)         --> 10  (the global one)


           end


          


           print(x)           --> 10  (the global one)


       注意,如果在交互模式下上面的例子可能不能输出期望的结果,因为第二句local i=1是一个完整的chunk,在交互模式下执行完这一句后,Lua将开始一个新的chunk,这样第二句的i已经超出了他的有效范围.可以将这段代码放在do..end(相当于c/c++的{})块中.


       应该尽可能的使用局部变量,有两个好处:1.避免命名冲突2.访问局部变量的速度比全局变量更快.


       我们给block划定一个明确的界限:do..end内的部分.当你想更好的控制局部变量的作用范围的时候这是很有用的.


              do


             local a2 = 2*a


             local d = sqrt(b^2 - 4*a*c)


             x1 = (-b + d)/a2


             x2 = (-b - d)/a2


           end          -- scope of 'a2' and 'd' ends here


           print(x1, x2)

4.3    控制结构语句


       控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,其他值为真.


       if语句,有三种形式:


              if conditions then


                     then-part


              end;


             


              if conditions then


                     then-part


              else


                     else-part


              end;


             


              if conditions then


                     then-part


              elseif conditions then


                     elseif-part


              ..                                 --->多个elseif


              else


                     else-part


              end;


       while语句:


              while condition do


                     statements;


              end;


       repeat-until语句:


              repeat


                     statements;


              until conditions;


       for语句有两大类:


       第一,数值for循环:


    for var=exp1,exp2,exp3 do


      loop-part


    end


    for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part;exp3可以省略,默认step=1


       有几点需要注意:


       1.三个表达式只会被计算一次,并且是在循环开始前.


              for i=1,f(x) do print(i) end


           for i=10,1,-1 do print(i) end


           第一个例子f(x)只会在循环前被调用一次


   

 楼主| 发表于 2007-5-10 11:39:16 | 显示全部楼层
    2.控制变量var是局部变量自动被声明,并且只在循环内有效.


              for i=1,10 do print(i) end


           max = i      -- probably wrong! 'i' here is global


       如果需要保留控制变量的值,需要在循环中将其保存


              -- find a value in a list


           local found = nil


           for i=1,a.n do


             if a == value then


               found = i      -- save value of 'i'


               break


             end


           end


           print(found)


       3.循环过程中不要改变控制变量的值,那样做的结果是不可预知的.


       如果要退出循环,使用break语句.


    第二,范型for循环:


           前面已经见过一个例子:


           -- print all values of array 'a'       


        for i,v in ipairs(a) do print(v) end


    范型for遍历迭代子函数返回的每一个值.


           再看一个遍历表key的例子:


           -- print all keys of table 't'      


        for k in pairs(t) do print(k) end


    范型for和数值for有两点相同:1.控制变量是局部变量,2.不要修改控制变量的值  


           再看一个例子,假定有一个表:


           days = {"Sunday", "Monday", "Tuesday", "Wednesday",


                    "Thursday", "Friday", "Saturday"}     


       


        现在想把对应的名字转换成星期几,一个有效地解决问题的方式是构造一个反向表:


        revDays = {["Sunday"] = 1, ["Monday"] = 2,        


                        ["Tuesday"] = 3, ["Wednesday"] = 4,


                        ["Thursday"] = 5, ["Friday"] = 6, 


                        ["Saturday"] = 7}                 


        下面就可以很容易获取问题的答案了:


        x = "Tuesday"                


            print(revDays[x])    --> 3


        我们不需要手工,可以自动构造反向表


        revDays = {}                 


            for i,v in ipairs(days) do


              revDays[v] = i         


            end                      


       如果你对范型for还有些不清楚在后面的章节我们会继续来学习.


4.4    break和return语句


       break语句用来退出当前循环(for,repeat,while).在循环外部不可以使用.


       return用来从函数返回结果,当一个函数自然结束结尾会有一个默认的return.(这种函数类似pascal的过程)


       Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前),例如:


              local i = 1


           while a do


             if a == v then break end


             i = i + 1


           end


       有时候为了调试或者其他目的需要在block的中间使用return或者break,可以显式的使用do..end来实现:


              function foo ()                                         


          return          --<< SYNTAX ERROR                 


          -- 'return' is the last statement in the next block


          do return end   -- OK                             


          ...             -- statements not reached         


        end

                      

 楼主| 发表于 2007-5-10 11:40:01 | 显示全部楼层

Lua用户手册(5)-函数
函数有两种用途:1.完成指定的任务,这种情况下函数作为调用语句使用2.计算并返回值,这种情况下函数作为赋值语句的表达式使用.


语法:


function func_name (arguments-list)


       statements-list;


end;


调用函数的时候,如果参数列表为空,必须使用()表明是函数调用.


       print(8*9, 9/8)


    a = math.sin(3) + math.cos(10)


    print(os.date())


上述规则有一个例外,当函数只有一个参数并且这个参数是字符串或者表构造的时候,()是可选的:


       print "Hello World"     <-->     print("Hello World")


    dofile 'a.lua'          <-->     dofile ('a.lua')


    print [[a multi-line    <-->     print([[a multi-line


     message]]                        message]])


    f{x=10, y=20}           <-->     f({x=10, y=20})


    type{}                  <-->     type({})


Lua也提供了面向对象方式调用函数的语法,比如o:foo(x)与o.foo(o, x)是等价的,后面的章节会详细介绍面向对象内容.


Lua使用的函数可以是Lua编写也可以是其他语言编写,对于Lua程序员来说用什么语言实现的函数使用起来都一样.


Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足.


       function f(a, b) return a or b end


      


       CALL             PARAMETERS


      


    f(3)             a=3, b=nil


    f(3, 4)          a=3, b=4


    f(3, 4, 5)       a=3, b=4   (5 is discarded)


5.1    返回多个结果值


Lua函数可以返回多个结果值,一些预定义的函数返回多值比如string.find,他返回匹配串的开始和结束下标(如果不存在匹配串返回nil).


       s, e = string.find("hello Lua users", "Lua")


   


    print(s, e)   -->  7      9


Lua函数中,在return后列出要返回的值得列表即可返回多值,如:


       function maximum (a)


      local mi = 1          -- maximum index


      local m = a[mi]       -- maximum value


      for i,val in ipairs(a) do


        if val > m then


          mi = i


          m = val


        end


      end


      return m, mi


    end


   


    print(maximum({8,10,23,12,5}))     --> 23   3


Lua总是调整函数返回值的个数去适用调用环境,当作为一个语句调用函数时,所有返回值被忽略.


       function foo0 () end                  -- returns no results


    function foo1 () return 'a' end       -- returns 1 result


    function foo2 () return 'a','b' end   -- returns 2 results


第一,当作为表达式调用函数时:有以下几种情况:


       1.当调用作为表达式最后一个参数或者仅有一个参数时,根据变量个数函数尽可能多地返回多个值,不足补nil,超出舍去.


       2.其他情况下,函数调用仅返回第一个值(如果没有返回值为nil)


       x,y = foo2()        -- x='a', y='b'


    x = foo2()          -- x='a', 'b' is discarded


    x,y,z = 10,foo2()   -- x=10, y='a', z='b'


      


       x,y = foo0()      -- x=nil, y=nil


    x,y = foo1()      -- x='a', y=nil


    x,y,z = foo2()    -- x='a', y='b', z=nil


      


       x,y = foo2(), 20      -- x='a', y=20


    x,y = foo0(), 20, 30  -- x='nil', y=20, 30 is discarded


第二,函数调用作为函数参数被调用时,和多值赋值是相同.


       print(foo0())          -->


    print(foo1())          -->  a


    print(foo2())          -->  a   b


    print(foo2(), 1)       -->  a   1


    print(foo2() .. "x")   -->  ax        


第三,函数调用在表构造函数中初始化时,和多值赋值时相同.


       a = {foo0()}         -- a = {}  (an empty table)


    a = {foo1()}         -- a = {'a'}


    a = {foo2()}         -- a = {'a', 'b'}


      


       a = {foo0(), foo2(), 4}   -- a[1] = nil, a[2] = 'a', a[3] = 4


另外,return f()这种类型的返回f()返回的所有值


       function foo (i)


      if i == 0 then return foo0()


      elseif i == 1 then return foo1()


      elseif i == 2 then return foo2()


      end


    end


   


    print(foo(1))     --> a


    print(foo(2))     --> a  b


    print(foo(0))     -- (no results)


    print(foo(3))     -- (no results)


可以使用圆括号强制使调用返回一个值.


       print((foo0()))        --> nil


    print((foo1()))        --> a


    print((foo2()))        --> a


一个return语句如果使用圆括号将返回值括起来也将导致返回一个值.


函数多值返回的特殊函数unpack,接受一个数组作为输入参数,返回数组的所有元素.unpack被用来实现范型调用机制,在C语言中可以使用函数指针调用可变的函数,可以声明参数可变的函数,但不能两者同时可变.在Lua中如果你想调用可变参数的可变函数只需要这样:


       f(unpack(a))


unpack返回a所有的元素作为f()的参数


       f = string.find


    a = {"hello", "ll"}


       print(f(unpack(a)))               -->3 4


预定义的unpack函数是用C语言实现的,我们也可以用Lua来完成:


       function unpack (t, i)


      i = i or 1


      if t then


        return t, unpack(t, i + 1)


      end


    end


5.2    可变参数


Lua函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(...)表示函数有可变的参数.Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数.


例如,我们可以重写print函数:


       printResult = ""


   


    function print (...)


      for i,v in ipairs(arg) do


        printResult = printResult .. tostring(v) .. "\t"


      end


      printResult = printResult .. "\n"


    end


有时候我们可能需要几个固定参数加上可变参数


       function g (a, b, ...) end


      


       CALL            PARAMETERS


      


    g(3)             a=3, b=nil, arg={n=0}


    g(3, 4)          a=3, b=4, arg={n=0}


    g(3, 4, 5, 8)    a=3, b=4, arg={5, 8; n=2}


如上面所示,Lua会将前面的实参传给函数的固定参数,后面的实参放在arg表中.


举个具体的例子,如果我们只想要string.find返回的第二个值:


一个典型的方法是使用虚变量(下划线)


       local _, x = string.find(s, p)


    -- now use `x'


    ...


还可以利用可变参数声明一个select函数:


       function select (n, ...)


      return arg[n]


    end


       print(string.find("hello hello", " hel"))         --> 6  9


    print(select(1, string.find("hello hello", " hel"))) --> 6


    print(select(2, string.find("hello hello", " hel"))) --> 9


有时候需要将函数的可变参数传递给另外的函数调用,可以使用前面我们说过的unpack(arg)返回arg表所有的可变参数,Lua提供了一个文本格式化的函数string.format(类似C语言的sprintf函数):


       function fwrite (fmt, ...)


      return io.write(string.format(fmt, unpack(arg)))


    end


这个例子将文本格式化操作和写操作组合为一个函数.


5.3    命名参数


Lua的函数参数是和位置相关的,调用时实参会按顺序依次传给形参.有时候用名字指定参数是很有用的,比如rename函数用来给一个文件重命名,有时候我们我们记不清命名前后两个参数的顺序了:


       -- invalid code


    rename(old="temp.lua", new="temp1.lua")


上面这段代码是无效的,Lua可以通过将所有的参数放在一个表中,把表作为函数的唯一参数来实现上面这段伪代码的功能.因为Lua语法支持函数调用时实参可以是表的构造.


       rename{old="temp.lua", new="temp1.lua"}


根据这个想法我们重定义了rename:


       function rename (arg)


      return os.rename(arg.old, arg.new)


    end


当函数的参数很多的时候,这种函数参数的传递方式很方便的.例如GUI库中创建窗体的函数有很多参数并且大部分参数是可选的,可以用下面这种方式:


       w = Window{ x=0, y=0, width=300, height=200,


                title = "Lua", background="blue",


                border = true


              }


             


       function Window (options)


      -- check mandatory options


      if type(options.title) ~= "string" then


        error("no title")


      elseif type(options.width) ~= "number" then


        error("no width")


      elseif type(options.height) ~= "number" then


        error("no height")


      end


   


      -- everything else is optional


      _Window(options.title,


              options.x or 0,    -- default value


              options.y or 0,    -- default value


              options.width, options.height,


              options.background or "white",   -- default


              options.border      -- default is false (nil)


             )


    end

 楼主| 发表于 2007-5-10 11:40:44 | 显示全部楼层

lua用户手册(6)-依然是函数
Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values).


       第一类值指:在Lua中函数和其他值(数值,字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值.


       词法定界指:被嵌套的函数可以访问他外部函数中的变量.这一特性给Lua提供了强大的编程能力.


       Lua中关于函数稍微难以理解的是函数也可以没有名字,匿名的.当我们提到函数名(比如print),实际上是说一个指向函数的变量,像持有其他类型值的变量一样


              a = {p = print}


           a.p("Hello World") --> Hello World


           print = math.sin  -- `print' now refers to the sine function


           a.p(print(1))     --> 0.841470


           sin = a.p         -- `sin' now refers to the print function


           sin(10, 20)       --> 10      20


       既然函数是值,那么表达式也可以创建函数了,Lua中我们经常这样写:


              function foo (x) return 2*x end


       这实际上是利用Lua提供的"语法上的甜头"(syntactic sugar)的结果,下面是原本的函数:


              foo = function (x) return 2*x end


       函数定义实际上是一个赋值语句,将类型为function的变量赋给一个变量.我们使用function (x) ... end 来定义一个函数和使用{}创建一个表一样.


       table标准库提供一个排序函数,接受一个表作为输入参数并且排序表中的元素.这个函数必须能够对不同类型的值(字符串或者数值)按升序或者降序进行排序.Lua不是尽可能多地提供参数来满足这些情况的需要,而是接受一个排序函数作为参数(类似C++的函数对象),排序函数接受两个排序元素作为输入参数,并且返回两者的大小关系,例如:


              network = {


              {name = "grauna",  IP = "210.26.30.34"},


              {name = "arraial", IP = "210.26.30.23"},


              {name = "lua",     IP = "210.26.23.12"},


              {name = "derain",  IP = "210.26.23.20"},


            }


       如果我们想通过表的name域排序:


              table.sort(network, function (a,b)


                 return (a.name > b.name)


           end)


       以其他函数作为参数的函数在Lua中被称作高级函数,高级函数在Lua中并没有特权,只是Lua把函数当作第一类函数处理的一个简单的结果.


下面给出一个绘图函数的例子:


              function eraseTerminal ()                                   


              io.write("\27[2J")                                    


            end                                                     


                                                            


            -- writes an `*' at column `x' , row `y'                


            function mark (x,y)                                     


              io.write(string.format("\27[%d;%dH*", y, x))          


            end                                                     


                                                            


            -- Terminal size                                        


            TermSize = {w = 80, h = 24}                             


                                                                    


            -- plot a function                                      


            -- (assume that domain and image are in the range [-1,1])


            function plot (f)                                       


              eraseTerminal()                                       


              for i=1,TermSize.w do                                 


                 local x = (i/TermSize.w)*2 - 1                     


                 local y = (f(x) + 1)/2 * TermSize.h                


                 mark(i, y)                                         


              end                                                   


              io.read()  -- wait before spoiling the screen         


            end                                                     


       要想让这个例子正确的运行,你必须调整你的终端类型和代码中的控制符一致


              plot(function (x) return math.sin(x*2*math.pi) end)


       将在屏幕上输出一个正弦曲线.


       将第一类值函数应用在表中是Lua实现面向对象和包机制的关键,这部分内容在后面章节介绍


 楼主| 发表于 2007-5-10 11:41:21 | 显示全部楼层
6.1    闭包


       当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界.虽然这看起来很清楚,事实并非如此,词法定界加上第一类函数在编程语言里是一个功能强大的概念,很少语言提供这种支持.


       下面看一个简单的例子,假定有一个学生姓名的列表和一个学生名和成绩对应的表;现在想根据学生的成绩从高到低对学生进行排序,可以这样做:


       names = {"Peter", "Paul", "Mary"}


    grades = {Mary = 10, Paul = 7, Peter = 8}


    table.sort(names, function (n1, n2)


      return grades[n1] > grades[n2]    -- compare the grades


    end)


       假定创建一个函数实现此功能:


       function sortbygrade (names, grades)


      table.sort(names, function (n1, n2)


        return grades[n1] > grades[n2]    -- compare the grades


      end)


    end


       例子中包含在sortbygrade函数内部的sort中的匿名函数可以访问sortbygrade的参数grades,在匿名函数内部grades不是全局变量也不是局部变量,我们称作外部的局部变量(external local variable)或者upvalue.(upvalue意思有些误导,然而在Lua中他的存在有历史的根源,还有他比起external local variable简短).


       看下面的代码 :


       function newCounter ()


      local i = 0


      return function ()   -- anonymous function


               i = i + 1


               return i


             end


    end


   


    c1 = newCounter()


    print(c1())  --> 1


    print(c1())  --> 2


       匿名函数使用upvalue i保存他的计数,当我们调用匿名函数的时候i已经超出了作用范围,因为创建i的函数newCounter已经返回了.然而Lua用闭包的思想正确处理了这种情况.简单的说闭包是一个函数加上它可以正确访问的upvalues.如果我们再次调用newCounter,将创建一个新的局部变量i,因此我们得到了一个作用在新的变量i上的新闭包.


       c2 = newCounter()


    print(c2())  --> 1


    print(c1())  --> 3


    print(c2())  --> 2


       c1,c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包.


       技术上来讲,闭包指值而不是指函数,函数仅仅是闭包的一个原型声明;尽管如此,在不会导致混淆的情况下我们继续使用术语函数代指闭包.


       闭包在上下文环境中提供很有用的功能,如前面我们见到的可以作为高级函数(sort)的参数;作为函数嵌套的函数(newCounter).这一机制使得我们可以在Lua的函数世界里组合出奇幻的编程技术.闭包也可用在回调函数中,比如在GUI环境中你需要创建一系列button,但用户按下button时回调函数被调用,可能不同的按钮被按下时需要处理的任务有点区别.具体来讲,一个十进制计算器需要10个相似的按钮,每个按钮对应一个数字,可以使用下面的函数创建他们:


       function digitButton (digit)


      return Button{ label = digit,


                     action = function ()


                                add_to_display(digit)


                              end


                   }


    end


       这个例子中我们假定Button是一个用来创建新按钮的工具,label是按钮的标签,action是按钮被按下时调用的回调函数.(实际上是一个闭包,因为他访问upvalue digit).digitButton完成任务返回后,局部变量digit超出范围,回调函数仍然可以被调用并且可以访问局部变量digit.


      闭包在完全不同的上下文中也是很有用途的.因为函数被存储在普通的变量内我们可以很方便的重定义或者预定义函数.通常当你需要原始函数有一个新的实现时可以重定义函数.例如你可以重定义sin使其接受一个度数而不是弧度作为参数:


       oldSin = math.sin


    math.sin = function (x)


      return oldSin(x*math.pi/180)


    end


       更清楚的方式:


       do

 local oldSin = math.sin


      local k = math.pi/180


      math.sin = function (x)


        return oldSin(x*k)


      end


    end


       这样我们把原始版本放在一个局部变量内,访问sin的唯一方式是通过新版本的函数.


       利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和java里的沙箱一样),当我们运行一段不信任的代码(比如我们运行网络服务器上获取的代码)时安全的环境是需要的,比如我们可以使用闭包重定义io库的open函数来限制程序打开的文件.


       do


      local oldOpen = io.open


      io.open = function (filename, mode)


        if access_OK(filename, mode) then


          return oldOpen(filename, mode)


        else


          return nil, "access denied"


        end


      end


    end


6.2    非全局函数


       Lua中函数可以作为全局变量也可以作为局部变量,我们已经看到一些例子:函数作为table的域(大部分Lua标准库使用这种机制来实现的比如io.read;math.sin).这种情况下,必须注意函数和表语法:


       1.表和函数放在一起


              Lib = {}


           Lib.foo = function (x,y) return x + y end


           Lib.goo = function (x,y) return x - y end


       2.使用表构造函数


              Lib = {


                 foo = function (x,y) return x + y end,


                 goo = function (x,y) return x - y end


           }


       3.Lua提供另一种语法方式


              Lib = {}


           function Lib.foo (x,y)


                 return x + y


           end


           function Lib.goo (x,y)


                 return x - y


           end


       当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效.这种定义在包中是非常有用的:因为Lua把chunk当作函数处理,在chunk内可以声明局部函数(仅仅在chunk内可见),词法定界保证了包内的其他函数可以调用此函数.下面是声明局部函数的两种方式:


       1.方式一


              local f = function (...)


             ...


           end


          


           local g = function (...)


             ...


             f()   -- external local `f' is visible here


             ...


           end


       2.方式二


              local function f (...)


             ...


           end


       有一点需要注意的是在声明递归局部函数的方式:


             local fact = function (n)


                 if n == 0 then return 1


                 else return n*fact(n-1)   -- buggy


                 end


           end


    上面这种方式导致Lua编译时遇到fact(n-1)并不知道他是局部函数fact,Lua会去查找是否有这样的全局函数fact.为了解决这个问题我们必须在定义函数以前先声明:


           local fact


           fact = function (n)


             if n == 0 then return 1


             else return n*fact(n-1)


             end


           end


       这样在fact内部fact(n-1)调用是一个局部函数调用,运行时fact就可以获取正确的值了.


       但是Lua扩展了他的语法使得可以在直接递归函数定义时使用两种方式都可以.


       在定义非直接递归局部函数时要先声明然后定义才可以:


              local f, g    -- `forward' declarations


                                              


        function g ()                     


          ...  f() ...                    


        end                               


                                          


        function f ()                     


          ...  g() ...                    


        end                


 楼主| 发表于 2007-5-10 11:41:39 | 显示全部楼层

6.3    正确的尾调用(Proper Tail Calls)


       Lua中函数的另一个有趣的特征是可以正确的处理尾调用.(一些作者使用术语尾递归[原文:proper tail recursion],虽然并未涉及到递归的概念) .


       尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用.例如:


              function f (x)


             return g(x)


           end


    g的调用是尾调用.


    例子中f调用g后不会再做任何事情,这种情况下当被调用函数g结束时程序不需要返回到调用者f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息.一些编译器比如Lua解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用.


    由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的.例如下面调用不论n为何值不会导致栈溢出.


           function foo (n)


             if n > 0 then return foo(n - 1) end


           end


       需要注意的是:必须明确什么是尾调用.


       一些调用者函数调用其他函数后也没有做其他的事情但不属于尾调用.比如:


              function f (x)


              g(x)         


              return 


        end      


    上面这个例子中f在调用g后,不得不丢弃g地返回值,所以不是尾调用,同样的下面几个例子也不时尾调用:


           return g(x) + 1     -- must do the addition      


        return x or g(x)    -- must adjust to 1 result


        return (g(x))       -- must adjust to 1 result


    Lua中类似return g(...) 这种格式的调用是尾调用.但是g和g的参数都可以是复杂表达式,因为Lua会在调用之前计算表达式的值.例如下面的调用是尾调用:


           return x.foo(x[j] + a*b, i + j)


       可以将尾调用理解成一种goto, 在状态机的编程领域尾调用是非常有用的.状态机的应用要求函数记住每一个状态,改变状态只需要goto(or call)一个特定的函数.我们考虑一个迷宫游戏作为例子:迷宫有很多个房间,每个房间有东西南北四个门,每一步输入一个移动的方向,如果该方向存在即到达该方向对应的房间,否则程序打印警告信息.目标是:从开始的房间到达目的房间.


       这个迷宫游戏是典型的状态机,每个当前的房间是一个状态.我们可以对每个房间写一个函数实现这个迷宫游戏,我们使用尾调用从一个房间移动到另外一个房间.一个四个房间的迷宫代码如下:


       function room1 ()


      local move = io.read()


      if move == "south" then return room3()


      elseif move == "east" then return room2()


      else print("invalid move")


           return room1()   -- stay in the same room


      end


    end


   


    function room2 ()


      local move = io.read()


      if move == "south" then return room4()


      elseif move == "west" then return room1()


      else print("invalid move")


           return room2()


      end


    end


   


    function room3 ()


      local move = io.read()


      if move == "north" then return room1()


      elseif move == "east" then return room4()


      else print("invalid move")


           return room3()


      end


    end


   


    function room4 ()


      print("congratilations!")


    end


       我们可以调用room1()开始这个游戏.


       如果没有正确的尾调用,每次移动都要创建一个栈,多次移动后可能导致栈溢出.但正确的尾调用可以无限制的尾调用,因为每次尾调用只是一个goto到另外一个函数并不是传统的函数调用.


       


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-5-6 14:52

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表