zoukankan      html  css  js  c++  java
  • Lua2

    1. 迭代器与Closure

    在Lua中,迭代器通常为函数,每调用一次函数,即返回集合中的“下一个”元素。每个迭代器都需要在每次成功调用之间保持一些状态,这样才能知道它所在的位置和下一次遍历时的位置。

    从这一点看,Lua中closure机制为此问题提供了语言上的保障,见如下示例:

    function values(t)
        local i = 0
        return function()
            i = i + 1
            return t[i]
        end
    end
    
    t = {10, 20, 30}
    it = values(t)
    while true do
        local element = it()
        if element == nil then
            break
        end
        print(element)
    end
    --输出结果为:
    --10
    --20
    --30
    
    --另外一种基于foreach的调用方式(泛型for)
    t2 = {15, 25, 35}
    for element in values(t2) do
        print(element)
    end
    --输出结果为:
    --15
    --25
    --35

    从上面的应用示例来看,相比于while方式,泛型for的方式提供了更清晰的实现逻辑。因为Lua在其内部替我们保存了迭代器函数,并在每次迭代时调用该隐式的内部迭代器,直到迭代器返回nil时结束循环。

    2. 泛型for的语义

    上面示例中的迭代器有一个明显的缺点,即每次循环时都需要创建一个新的closure变量,否则第一次迭代成功后,再将该closure用于新的for循环时将会直接退出。
    这里我们还是先详细的讲解一下Lua中泛型(for)的机制,之后再给出一个无状态迭代器的例子,以便于我们的理解。如果我们的迭代器实现为无状态迭代器,那么就不必为每一次的泛型(for)都重新声明一个新的迭代器变量了。
    泛型(for)的语法如下:

    for <var-list> in <exp-list> do
            <body>
    end

    为了便于理解,由于我们在实际应用中<exp-list>通常只是包含一个表达式(expr),因此简单起见,这里的说明将只是包含一个表达式,而不是表达式列表。现在我们先给出表达式的原型和实例,如:

    function ipairs2(a)
         return iter,a,0
    end

    该函数返回3个值,第一个为实际的迭代器函数变量,第二个是一个恒定对象,这里我们可以理解为待遍历的容器,第三个变量是在调用iter()函数时为其传入的初始值。
    下面我们再看一下iter()函数的实现,如:

    local function iter(a, i)
        i = i + 1
        local v = a[i]
        if v then
            return i, v
        else
            return nil, nil
        end
    end

    在迭代器函数iter()中返回了两个值,分别对应于table的key和value,其中key(返回的i)如果为nil,泛型(for)将会认为本次迭代已经结束。下面我们先看一下实际用例,如:

    function ipairs2(a)
        return iter,a,0
    end
    
    
    local function iter(a, i)
        i = i + 1
        local v = a[i]
        if v then
            return i, v
        else
            return nil, nil
        end
    end
    
    a = {"one","two","three"}
    for k,v in ipairs2(a) do
        print(k, v)
    end
    --输出结果为:
    --1       one
    --2       two
    --3       three

    这个例子中的泛型(for)写法可以展开为下面的基于while循环的方式,如:

    local function iter(a, i)
        i = i + 1
        local v = a[i]
        if v then
            return i, v
        else
            return nil, nil
        end
    end
    
    function ipairs2(a)
        return iter,a,0
    end
    
    a = {"one","two","three"}
    do
        local _it,_s,_var = ipairs2(a)
        while true do
            local var_1,var_2 = _it(_s,_var)
            _var = var_1
            if _var == nil then  --注意,这里只判断迭代器函数返回的第一个是否为nil。
                break
            end
            print(var_1,var_2)
        end
    end
    --输出结果同上。

    3.编译

    Lua中提供了dofile函数,它是一种内置的操作,用于运行Lua代码块。但实际上dofile只是一个辅助函数,loadfile才是真正的核心函数。相比于dofile,loadfile只是从指定的文件中加载Lua代码块,然后编译这段代码块,如果有编译错误,就返回nil,同时给出错误信息,但是在编译成功后并不真正的执行这段代码块。因此,我们可以将dofile实现为:

    function dofile(filename)
         local f = assert(loadfile(filename))
         return f()
    end

    这里如果loadfile执行失败,assert函数将直接引发一个错误(assert函数将检查其第一个参数是否为true,如果是,则简单的返回该参数,否则就引发一个错误。)。

    通过dofile的代码,我们还可以看出,如果打算多次运行一个文件中的Lua代码块,我们可以只执行loadfile一次,之后多次运行它返回的结果即可,

    这样就可以节省多次编译所带来的开销。这一点也是loadfile和dofile在性能上的区别。

    Lua中还提供了另外一种动态执行Lua代码的方式,即loadstring函数。顾名思义,相比于loadfile,loadstring的代码源来自于其参数中的字符串,如:
    f = loadstring("i = i + 1")
    此时f就变成了一个函数,每次调用时就执行"i = i + 1",如:

    i = 0
    f()  
    print(i) --将输出1
    f()
    print(i) --将输出2

    loadstring确实是一个功能强大的函数,但是由此而换来的性能开销也是我们不得不考虑的事情。所以对于很多常量字符串如果仍然使用loadstring方式,那就没有太大意义了

    如上面的例子f = loadstring("i = i + 1"),因为我们完全可以通过f = function () i = i + 1 end的形式取而代之。

    而后者的执行效率要远远高于前者。毕竟后者只编译一次,而前者则在每次调用loadstring时均被编译。

    对于loadstring,我们还需要注意的是,该函数总是在全局环境中编译它的字符串,因此它将无法访问局部变量,而是只能访问全局变量,如:

    i = 32
    local i = 5
    f = loadstring("i = i + 1; print(i)")
    f()   --因为f函数loadstring只能访问全局变量i,因此输出33

     而function会受全局变量的影响

    i = 32
    g = function() i = i + 1; print(i) end
    g()  --33
    local i = 5
    g()  --34
    
    
    local j = 32
    g = function() j = j + 1; print(j) end
    g()  --33
    j = 5
    g()  --6

    顺便说下,脚本的执行顺序

    a.lua:
    i=11
    
    b.lua:
    print(i)  --输出nil
    import "a"
    i=22
    print(i)  --输出22

    对于loadstring返回的函数,如果需要对一个表达式求值,则必须在其之前添加return,这样才能构成一条语句,返回表达式的值,如:

    i = 32
    f = loadstring("i = i + 1; return i * 2")
    print(f()) --输出66
    print(f()) --输出68。由于loadstring返回的就是正规的函数,因此可以被反复调用。

    Lua将所有独立的程序块视为一个匿名函数的函数体,并且该匿名函数还具有可变长实参,因此在调用loadstring时,可以为其传递参数,如:

    local i = 30
    --下面的...表示变长实参,将值赋给局部变量x。
    local f = assert(loadstring("local x = ...; return (x + 10) * 2")) 
    for i = 1, 20 do
        print(string.rep("*",f(i)))   --string.rep("abc", 2) <== 返回"abcabc"
    end

    4、错误

    Lua作为一种嵌入式脚本语言,在发生错误时,不应该只是简单的退出或崩溃。相反,一旦有错误发生,Lua就应该结束当前程序块并返回到应用程序。
    在Lua中我们可以通过error()函数获取错误消息,如:

    if not xy then
    error("发生错误啦!")
    end

    5、错误处理与异常

    Lua提供了错误处理函数pcall,该函数的第一个参数为需要“保护执行”的函数,如果该函数执行失败,pcall将返回false及错误信息,否则返回true和函数调用的返回值。见如下代码:

    function foo()
        local a = 10
        print(a[2])
    end
    
    r, msg = pcall(foo)
    if r then
        print("This is ok.")
    else
        print("This is error.")
        print(msg)
    end
    --输出结果为:
    --This is error.
    --d:/test.lua:3: attempt to index local 'a' (a number value)

    我们也可以给pcall函数直接传递匿名函数,如:

    r, msg = pcall(function() error({code = 121}) end)
    if r then
        print("This is ok.")
    else
        print("This is error.")
        print(msg.code)
    end
    --输出结果为:
    --This is error.
    --121

    6、错误消息与追溯

    通常在错误发生时,希望得到更多的调试信息,而不是只有发生错误的位置。至少等追溯到发生错误时和函数调用情况,显示一个完整的函数调用栈轨迹。

    要完成这一功能,我们需要使用Lua提供的另外一个内置函数xpcall。该函数除了接受一个需要被调用的函数之外,还接受第二个参数,即错误处理函数。

    当发生错误时,Lua会在调用栈展开前调用错误处理函数。这样,我们就可以在这个函数中使用debug库的debug.traceback函数,它会根据调用栈来构建一个扩展的错误消息。如:

    function errorFunc()
        local a = 20
        print(a[10])
    end
    
    function errorHandle()
        print(debug.traceback())
    end
    
    if xpcall(errorFunc,errorHandle) then
        print("This is OK.")
    else
        print("This is error.")
    end
    
    --输出结果为:
    --[[stack traceback:
            d:/test.lua:7: in function <d:/test.lua:6>
            d:/test.lua:3: in function <d:/test.lua:1>
            [C]: in function 'xpcall'
            d:/test.lua:10: in main chunk
            [C]: ?
    This is error.
    --]]

    [转载]http://www.cnblogs.com/stephen-liu74/archive/2012/06/20/2413799.html

  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/MrZivChu/p/lua2.html
Copyright © 2011-2022 走看看