在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
上一篇编辑编辑着,发现,缩进出了问题。作为一个不是强迫症的人,实在是忍受不了同一级内容不同缩进方式的槽点,于是重开一篇吧。(万幸,这样的文章也只有我自己看。) 第四 基本语法 赋值语句,Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。 a, b = 10, 2*x <--> a=10; b=2*x 遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值: x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[i]' 当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略: a. 变量个数>值的个数按变量个数补足nil
b. 变量个数<值的个数多余的值会被忽略
控制结构语句 控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,其他值为真。 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; b、while语句: while condition do statements; end; c、repeat-until语句: repeat
statements;
until conditions;
d、for语句有两大类: for var=exp1,exp2,exp3 do loop-part end 有几点需要注意: 3、 循环过程中不要改变控制变量的值,那样做的结果是不可预知的。如果要退出循环,使用break语句。 第二,范型for循环: -- print all values of array 'a' for i,v in ipairs(a) do print(v) end 范型for遍历迭代子函数返回的每一个值。 -- print all keys of table 't' for k in pairs(t) do print(k) end 范型for和数值for有两点相同: 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 e、break和return语句 break语句用来退出当前循环(for,repeat,while)。在循环外部不可以使用。 local i = 1 while a[i] do if a[i] == 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 f、大家可以看出来,Lua内没有提供continue和switch语句。continue语句,可以用ifelse来实现,就是符合条件的执行部分代码,不符合条件的就else不执行功能代码。 而,switch用if elseif else end这样的语句来实现的话,就会让人恶心的不行不行的了。其中,有一种实现方法,可以借鉴。 Switch语句的替代语法(所有替代方案中觉得最好,最简洁,最高效,最能体现Lua特点的一种方案) action = { [1] = function (x) print(x) end, [2] = function (x) print( 2 * x ) end, ["nop"] = function (x) print(math.random()) end, ["my name"] = function (x) print("fred") end, } while true do key = getChar() x = math.ramdon() action[key](x) end 第五 函数 函数有两种用途:1.完成指定的任务,这种情况下函数作为调用语句使用;2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。 语法: function func_name (arguments-list) statements-list; end; 调用函数的时候,如果参数列表为空,必须使用()表明是函数调用。Lua也提供了面向对象方式调用函数的语法,比如o:foo(x)与o.foo(o, x)是等价的。在面向对象内这个比较容易让人搞混,下文会提到。 Lua使用的函数可以是Lua编写也可以是其他语言编写,对于Lua程序员来说用什么语言实现的函数使用起来都一样。 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) a、返回多个结果值 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 可以使用圆括号强制使调用返回一个值。一个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[i] then return t[i], unpack(t, i + 1) end end b、可变参数 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} c、命名参数 lua的函数参数是和位置相关的,调用时实参会按顺序依次传给形参。当函数的参数很多的时候,用函数参数的传递方式很方便的。例如GUI库中创建窗体的函数有很多参数并且大部分参数是可选的,可以用下面这种方式: w = Window { x=0, y=0, width=300, height=200, title = "Lua", background="blue", border = true } -- 注意这里是传入的表,而不是括号()
D、函数更深一层 Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。 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 函数定义实际上是一个赋值语句,将类型为function的变量赋给一个变量。我们使用function (x) ... end来定义一个函数和使用{}创建一个表一样。 foo = function (x) return 2*x end 原本函数是上面这种,但是可以利用Lua提供的“语法上的甜头”(syntactic sugar),用下面这种写法进行替代 function foo (x) return 2*x end 以其他函数作为参数的函数在Lua中被称作高级函数,高级函数在Lua中并没有特权,只是Lua把函数当作第一类函数处理的一个简单的结果。 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"}, } table.sort(network, function (a,b) return (a.name > b.name) end) 值得注意的是,Lua在进行排序时,对不稳定排序可能会抛出错误哦。这个问题,在本博客的另一篇中有提到。 i、闭包 当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。虽然这看起来很清楚,事实并非如此,词法定界加上第一类函数在编程语言里是一个功能强大的概念,很少语言提供这种支持。 下面看一个简单的例子,假定有一个学生姓名的列表和一个学生名和成绩对应的表;现在想根据学生的成绩从高到低对学生进行排序,可以这样做: 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是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。 function digitButton (digit) return Button{ label = digit, action = function () add_to_display(digit) end } 这个例子中我们假定Button是一个用来创建新按钮的工具, label是按钮的标签,action是按钮被按下时调用的回调函数。(实际上是一个闭包,因为他访问upvalue digit)。digitButton完成任务返回后,局部变量digit超出范围,回调函数仍然可以被调用并且可以访问局部变量digit。 ii、非全局函数 当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效。下面是声明局部函数的两种方式: local f = function (...) ... end local g = function (...) ... f() -- external local `f' is visible here ... end 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 iii、正确的尾调用 Lua中函数的另一个有趣的特征是可以正确的处理尾调用(proper tail recursion,一些书使用术语“尾递归”,虽然并未涉及到递归的概念)。 function f(x) return g(x) end Lua中类似return g(...)这种格式的调用是尾调用。但是g和g的参数都可以是复杂表达式,因为Lua会在调用之前计算表达式的值。例如下面的调用是尾调用: return x[i].foo(x[j] + a*b, i + j)
而下面的就不是尾调用 function f (x) g(x) return end 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 可以将尾调用理解成一种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()开始这个游戏。 |
请发表评论