zoukankan      html  css  js  c++  java
  • Lua语法基础(2)--基本语法、函数

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

    第四 基本语法

    赋值语句,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为假,其他值为真。
    a、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;

    b、while语句:

    while condition do
        statements;
    end;

    c、repeat-until语句:

    repeat
        statements;
    until conditions;

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

    for var=exp1,exp2,exp3 do
        loop-part
    end

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

    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

    e、break和return语句

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

    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程序员来说用什么语言实现的函数使用起来都一样。
    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)

    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) .. "	"
    end
    printResult = printResult .. "
    "
    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
    }  -- 注意这里是传入的表,而不是括号()
    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

    D、函数更深一层

    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

    函数定义实际上是一个赋值语句,将类型为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是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。
    技术上来讲,闭包指值而不是指函数,函数仅仅是闭包的一个原型声明;尽管如此,在不会导致混淆的情况下我们继续使用术语函数代指闭包。
    闭包在上下文环境中提供很有用的功能,如前面我们见到的可以作为高级函数(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。

    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,一些书使用术语“尾递归”,虽然并未涉及到递归的概念)。
    尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。例如:

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

  • 相关阅读:
    flex + bison multiple parsers
    Educational Codeforces Round 95 (Rated for Div. 2)
    python学习笔记 day20 序列化模块(二)
    python学习笔记 day20 常用模块(六)
    python 学习笔记 常用模块(五)
    python学习笔记 day19 常用模块(四)
    python学习笔记 day19 常用模块(三)
    python学习笔记 day19 常用模块(二)
    python学习笔记 day19 作业讲解-使用正则表达式实现计算器
    python学习笔记 day19 常用模块
  • 原文地址:https://www.cnblogs.com/slysky/p/5368049.html
Copyright © 2011-2022 走看看