上一篇编辑编辑着,发现,缩进出了问题。作为一个不是强迫症的人,实在是忍受不了同一级内容不同缩进方式的槽点,于是重开一篇吧。(万幸,这样的文章也只有我自己看。)

第四 基本语法

赋值语句,Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。

  1. a, b = , *x <--> a=; b=*x

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

  1. x, y = y, x -- swap 'x' for 'y'
  2. a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[i]'

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

  1. a. 变量个数>值的个数按变量个数补足nil
  2. b. 变量个数<值的个数多余的值会被忽略

控制结构语句

控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,其他值为真。
a、if语句,有三种形式:

  1. if conditions then
  2.   then-part
  3. end;
  4. if conditions then
  5.   then-part
  6. else
  7.   else-part
  8. end;
  9. if conditions then
  10.   then-part
  11. elseif conditions then
  12.   elseif-part
  13. .. --->多个elseif
  14. else
  15.   else-part
  16. end;

b、while语句:

  1. while condition do
  2. statements;
  3. end;

c、repeat-until语句:

  1. repeat
  2. statements;
  3. until conditions;

d、for语句有两大类:
第一,数值for循环:

  1. for var=exp1,exp2,exp3 do
  2. loop-part
  3. end

有几点需要注意:
1. 三个表达式只会被计算一次,并且是在循环开始前。2、 控制变量var是局部变量自动被声明,并且只在循环内有效.

3、 循环过程中不要改变控制变量的值,那样做的结果是不可预知的。如果要退出循环,使用break语句。

第二,范型for循环:
前面已经见过一个例子:

  1. -- print all values of array 'a'
  2. for i,v in ipairs(a) do print(v) end

范型for遍历迭代子函数返回的每一个值。
再看一个遍历表key的例子:

  1. -- print all keys of table 't'
  2. for k in pairs(t) do print(k) end

范型for和数值for有两点相同:
1. 控制变量是局部变量
2. 不要修改控制变量的值
再看一个例子,假定有一个表:

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

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

  1. revDays = {["Sunday"] = , ["Monday"] = ,
  2. ["Tuesday"] = , ["Wednesday"] = ,
  3. ["Thursday"] = , ["Friday"] = ,
  4. ["Saturday"] = }

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

  1. x = "Tuesday"
  2. print(revDays[x]) -->

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

  1. revDays = {}
  2. for i,v in ipairs(days) do
  3. revDays[v] = i
  4. end

e、break和return语句

break语句用来退出当前循环(for,repeat,while)。在循环外部不可以使用。
return用来从函数返回结果,当一个函数自然结束结尾会有一个默认的return。
Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前),例如:

  1. local i =
  2. while a[i] do
  3. if a[i] == v then break end
  4. i = i +
  5. end
  6.  
  7. 有时候为了调试或者其他目的需要在block的中间使用return或者break,可以显式的使用do..end来实现:
  8.  
  9. function foo ()
  10. return --<< SYNTAX ERROR
  11. -- 'return' is the last statement in the next block
  12. do return end -- OK
  13. ... -- statements not reached
  14. end

f、大家可以看出来,Lua内没有提供continue和switch语句。continue语句,可以用ifelse来实现,就是符合条件的执行部分代码,不符合条件的就else不执行功能代码。

而,switch用if elseif else end这样的语句来实现的话,就会让人恶心的不行不行的了。其中,有一种实现方法,可以借鉴。

Switch语句的替代语法(所有替代方案中觉得最好,最简洁,最高效,最能体现Lua特点的一种方案)

  1. action = {
  2. [] = function (x) print(x) end,
  3. [] = function (x) print( * x ) end,
  4. ["nop"] = function (x) print(math.random()) end,
  5. ["my name"] = function (x) print("fred") end,
  6. }
  7. while true do
  8. key = getChar()
  9. x = math.ramdon()
  10. action[key](x)
  11. end

第五 函数

函数有两种用途:1.完成指定的任务,这种情况下函数作为调用语句使用;2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。

语法:

  1. function func_name (arguments-list)
  2. statements-list;
  3. end;

调用函数的时候,如果参数列表为空,必须使用()表明是函数调用。Lua也提供了面向对象方式调用函数的语法,比如o:foo(x)与o.foo(o, x)是等价的。在面向对象内这个比较容易让人搞混,下文会提到。

Lua使用的函数可以是Lua编写也可以是其他语言编写,对于Lua程序员来说用什么语言实现的函数使用起来都一样。
Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足。

  1. function f(a, b) return a or b end
  2. CALL PARAMETERS
  3. f() a=, b=nil
  4. f(, ) a=, b=
  5. f(, , ) a=, b= ( is discarded)

a、返回多个结果值

Lua函数可以返回多个结果值,比如string.find,其返回匹配串“开始和结束的下标”(如果不存在匹配串返回nil)。

  1. s, e = string.find("hello Lua users", "Lua")
  2. print(s, e) -->
  3.  
  4. Lua函数中,在return后列出要返回的值得列表即可返回多值,如:
  5.  
  6. function maximum (a)
  7. local mi = -- maximum index
  8. local m = a[mi] -- maximum value
  9. for i,val in ipairs(a) do
  10. if val > m then
  11. mi = i
  12. m = val
  13. end
  14. end
  15. return m, mi
  16. end
  17. print(maximum({,,,,})) -->

可以使用圆括号强制使调用返回一个值。一个return语句如果使用圆括号将返回值括起来也将导致返回一个值。

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

  1. f(unpack(a))

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

  1. f = string.find
  2. a = {"hello", "ll"}
  3. print(f(unpack(a))) -->
  4.  
  5. 预定义的unpack函数是用C语言实现的,我们也可以用Lua来完成:
  6.  
  7. function unpack(t, i)
  8. i = i or
  9. if t[i] then
  10. return t[i], unpack(t, i + )
  11. end
  12. end

b、可变参数

Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数。

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

  1. printResult = ""
  2. function print(...)
  3. for i,v in ipairs(arg) do
  4. printResult = printResult .. tostring(v) .. "\t"
  5. end
  6. printResult = printResult .. "\n"
  7. end
  8.  
  9. 有时候我们可能需要几个固定参数加上可变参数
  10.  
  11. function g (a, b, ...) end
  12. CALL PARAMETERS
  13. g() a=, b=nil, arg={n=}
  14. g(, ) a=, b=, arg={n=}
  15. g(, , , ) a=, b=, arg={, ; n=}

c、命名参数

lua的函数参数是和位置相关的,调用时实参会按顺序依次传给形参。当函数的参数很多的时候,用函数参数的传递方式很方便的。例如GUI库中创建窗体的函数有很多参数并且大部分参数是可选的,可以用下面这种方式:

  1. w = Window {
  2. x=, y=, width=, height=,
  3. title = "Lua", background="blue",
  4. border = true
  5. } -- 注意这里是传入的表,而不是括号()
  6. function Window (options)
  7. -- check mandatory options
  8. if type(options.title) ~= "string" then
  9.   error("no title")
  10. elseif type(options.width) ~= "number" then
  11.   error("no width")
  12. elseif type(options.height) ~= "number" then
  13.   error("no height")
  14. end
  15. -- everything else is optional
  16. _Window(options.title,
  17. options.x or , -- default value
  18. options.y or , -- default value
  19. options.width, options.height,
  20. options.background or "white", -- default
  21. options.border -- default is false (nil)
  22. )
  23. end

D、函数更深一层

Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
第一类值指:在Lua中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
词法定界指:被嵌套的函数可以访问他外部函数中的变量。这一特性给Lua提供了强大的编程能力。
Lua中关于函数稍微难以理解的是函数也可以没有名字,匿名的。当我们提到函数名(比如print),实际上是说一个指向函数的变量,像持有其他类型值的变量一样:

  1. a = {p = print}
  2. a.p("Hello World") --> Hello World
  3. print = math.sin -- `print' now refers to the sine function
  4. a.p(print()) --> 0.841470
  5. sin = a.p -- `sin' now refers to the print function
  6. sin(, ) -->

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

  1. foo = function (x) return *x end
  2.  
  3. 原本函数是上面这种,但是可以利用Lua提供的“语法上的甜头”(syntactic sugar),用下面这种写法进行替代
  4.  
  5. function foo (x) return *x end

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

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

  1. network = {
  2. {name = "grauna", IP = "210.26.30.34"},
  3. {name = "arraial", IP = "210.26.30.23"},
  4. {name = "lua", IP = "210.26.23.12"},
  5. {name = "derain", IP = "210.26.23.20"},
  6. }
  7.  
  8. table.sort(network, function (a,b)
  9. return (a.name > b.name)
  10. end)

值得注意的是,Lua在进行排序时,对不稳定排序可能会抛出错误哦。这个问题,在本博客的另一篇中有提到。

i、闭包

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

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

  1. names = {"Peter", "Paul", "Mary"}
  2. grades = {Mary = , Paul = , Peter = }
  3. table.sort(names, function (n1, n2)
  4.   return grades[n1] > grades[n2] -- compare the grades
  5. end)

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

  1. function sortbygrade (names, grades)
  2.   table.sort(names, function (n1, n2)
  3.     return grades[n1] > grades[n2] -- compare the grades
  4.   end)
  5. end

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

  1. function newCounter()
  2.   local i =
  3.     return function() -- anonymous function
  4.         i = i +
  5.         return i
  6.     end
  7. end
  8. c1 = newCounter()
  9. print(c1()) --> 1
  10. print(c1()) --> 2

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

  1. c2 = newCounter()
  2. print(c2()) --> 1
  3. print(c1()) --> 3
  4. print(c2()) --> 2

c1、c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。
技术上来讲,闭包指值而不是指函数,函数仅仅是闭包的一个原型声明;尽管如此,在不会导致混淆的情况下我们继续使用术语函数代指闭包。
闭包在上下文环境中提供很有用的功能,如前面我们见到的可以作为高级函数(sort)的参数;作为函数嵌套的函数(newCounter)。这一机制使得我们可以在Lua的函数世界里组合出奇幻的编程技术。闭包也可用在回调函数中,比如在GUI环境中你需要创建一系列button,但用户按下button时回调函数被调用,可能不同的按钮被按下时需要处理的任务有点区别。具体来讲,一个十进制计算器需要10个相似的按钮,每个按钮对应一个数字,可以使用下面的函数创建他们:

  1. function digitButton (digit)
  2. return Button{ label = digit,
  3. action = function ()
  4. add_to_display(digit)
  5. end
  6. }
  7.  
  8. end

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

ii、非全局函数

当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效。下面是声明局部函数的两种方式:

  1. local f = function (...)
  2. ...
  3. end
  4. local g = function (...)
  5. ...
  6. f() -- external local `f' is visible here
  7. ...
  8. end
  1. local function f (...)
  2. ...
  3. end

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

  1. local fact = function (n)
  2.   if n == then
  3.     return
  4.   else
  5.     return n*fact(n-) -- buggy
  6.   end
  7. end

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

  1. local fact
  2. fact = function (n)
  3.   if n == then
  4.     return
  5.   else
  6.     return n*fact(n-)
  7.   end
  8. end

iii、正确的尾调用

Lua中函数的另一个有趣的特征是可以正确的处理尾调用(proper tail recursion,一些书使用术语“尾递归”,虽然并未涉及到递归的概念)。
尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。例如:

  1. function f(x)
  2. return g(x)
  3. end

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

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

而下面的就不是尾调用

  1. function f (x)
  2. g(x)
  3. return
  4. end
  5.  
  6. return g(x) + -- must do the addition
  7. return x or g(x) -- must adjust to 1 result
  8. return (g(x)) -- must adjust to 1 result

可以将尾调用理解成一种goto,在状态机的编程领域尾调用是非常有用的。状态机的应用要求函数记住每一个状态,改变状态只需要goto(or call)一个特定的函数。

我们考虑一个迷宫游戏作为例子:迷宫有很多个房间,每个房间有东西南北四个门,每一步输入一个移动的方向,如果该方向存在即到达该方向对应的房间,否则程序打印警告信息。目标是:从开始的房间到达目的房间。
这个迷宫游戏是典型的状态机,每个当前的房间是一个状态。我们可以对每个房间写一个函数实现这个迷宫游戏,我们使用尾调用从一个房间移动到另外一个房间。一个四个房间的迷宫代码如下:

  1. function room1 ()
  2. local move = io.read()
  3. if move == "south" then
  4. return room3()
  5. elseif move == "east" then
  6. return room2()
  7. else
  8. print("invalid move")
  9. return room1() -- stay in the same room
  10.   end
  11. end
  12. function room2 ()
  13.   local move = io.read()
  14.   if move == "south" then
  15.     return room4()
  16.   elseif move == "west" then
  17.     return room1()
  18.   else
  19.     print("invalid move")
  20.     return room2()
  21.   end
  22. end
  23. function room3 ()
  24.   local move = io.read()
  25.   if move == "north" then
  26.     return room1()
  27.   elseif move == "east" then
  28.     return room4()
  29.   else
  30.     print("invalid move")
  31.     return room3()
  32.   end
  33. end
  34. function room4 ()
  35.   print("congratilations!")
  36. end

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

Lua语法基础(2)--基本语法、函数的更多相关文章

  1. Lua脚本之语法基础快速入门

    要 1.基本数据类型 2.Lua中的常用语句结构以及函数 3.Lua中的常用语句结构介绍 4.Lua中的库函数 目录[-] 一.基本数据类型 二.Lua中的常用语句结构以及函数 1.Lua中的常用语句 ...

  2. Lua语法基础(1)---简介、基本数据类型、表达式

    我觉得我已经陷入了一个坑内.因为,安装了Lua和SublimeText3编辑器之后,怎么使自己编写的lua代码在untiy内运行起来,是个我完全不了解的机制.先放一放吧.首先,来回顾一下Lua的语法基 ...

  3. Swift语法基础入门三(函数, 闭包)

    Swift语法基础入门三(函数, 闭包) 函数: 函数是用来完成特定任务的独立的代码块.你给一个函数起一个合适的名字,用来标识函数做什么,并且当函数需要执行的时候,这个名字会被用于“调用”函数 格式: ...

  4. JavaSE语法基础(3)---函数、数组

    JavaSE语法基础(3)---函数.数组 函数的概念:实现特定功能的一段代码,可反复使用. 函数的出现减少代码冗余,提高代码的复用性,可读性,可维护性,可以使每个功能模块独立起来,方便分工合作. 函 ...

  5. python面向对象的基础语法(dir内置函数、self参数、初始化方法、内置方法和属性)

    面相对象基础语法 目标 dir 内置函数 定义简单的类(只包含方法) 方法中的 self 参数 初始化方法 内置方法和属性 01. dir 内置函数(知道) 在 Python 中 对象几乎是无所不在的 ...

  6. python基础入门一(语法基础)

    作为自己正式接触并应用的第一门编程语言,在Alex和武sir两位大王的要求下,开始了写博客总结的日子.学习编程语言是很有趣的一件事情,但有2点请一定要谨记:1.做人靠自己,码代码也必须靠自己.能不能成 ...

  7. PHP语法基础

    1.PHP语法基础 PHP标记符 <?php ?> 常亮与变量 $a = 10; 变量 可以在运行过程中修改 $a = 10; $a = 20; $b = 5; echo $a+$b; c ...

  8. Javascript语法基础

    Javascript语法基础   一.基本数据类型   JavaScript中支持数字.字符串和布尔值三种基本数据类型: 1.数字 数字型是JavaScript中的基本数据类型.在JavaScript ...

  9. LinQ 语法基础

    LINQ (Language-Integrated Query,语言集成查询). LINQ to Objects.LINQ to SQL.LINQ to DataSet和LINQ to XML,它们分 ...

随机推荐

  1. 使用userAgent判断使用的是什么浏览器

    <script type="text/javascript"> function validB(){ var u_agent = Navigator.userAgent ...

  2. linux 的时区设置函数tzset() 【转】

    linux 的时区设置函数tzset() 本文转载于: http://blog.csdn.net/epicyong333/article/details/5258152 tzset #incude & ...

  3. Intel Edison学习笔记(一)—— 刷系统

    一.下载安装包 1.固件安装包:官网下载地址:http://downloadmirror.intel.com/ ... image-ww25.5-15.zip2 2.烧录工具下载地址:http://d ...

  4. Android 获取包名,版本信息

    Android 获取包名,版本信息及VersionName名称     <span style="font-size: 14px;">private String ge ...

  5. JAVA中如何将一个json形式的字符串转为json对象

    import java.io.*; import org.json.*; public class Demo { public static void main(String[] args) thro ...

  6. request.getRequestURI() 、request.getRequestURL() 、request.getContextPath()、request.getServletPath()区别

    request.getRequestURI() /jqueryWeb/resources/request.jsprequest.getRequestURL() http://localhost:808 ...

  7. Mysql按数字大小排序String字段

    问题是这样的,当我们按由大到小的顺序排序一组数字的时候,它应该如此: 9800 8000 900 但如果是这些数字是以String类型存储的话,直接排序的结果会是这样: 9800 900 8000 当 ...

  8. chrome表单自动填充导致input文本框背景变成偏黄色问题解决

    chrome表单自动填充后,input文本框的背景会变成偏黄色的,想必大家都会碰到这种情况吧, 这是由于chrome会默认给自动填充的input表单加上input:-webkit-autofill私有 ...

  9. systemctl 命令

    systemctl命令是系统服务管理器指令,它实际上将 service 和 chkconfig 这两个命令组合到一起. 任务 旧指令 新指令 使某服务自动启动 chkconfig --level 3 ...

  10. Objective-C 如何让非等宽的数字和空格对齐

    在printf中,我们可以通过格式字符串来对文字进行对齐输出,比如: printf("%5d\n%5d", 12, 345); 在使用等宽字体的Console中,我们可以看到数字右 ...