LUA数据结构(一)

ronald4个月前职场1500

Lua数据结构

    Lua有8个基本类型,分别为:nil、boolean、number、string、userdata、function、thread和table;我们可以通过函数type函数获取变量类型,如下:

a="cccd"
dict={}
print(type(a))
print(type(dict))
print(type(nil))

    其中nil、boolean、number、string比较简单;这里主要介绍userdata、function、thread、table四个类型。

table

    Lua中的table本质上是一个关联数组,数组的索引除了是nil,可以是数字、字符串、布尔类型、函数和table等,而数组的值,则可以是8个基本类型的任意值,如下:

function hello_world()
    print("hello world")
end

dict={}
dict_key={"aaaa"}
dict[hello_world]="a"
dict[dict_key]="vvvv"
dict["dddd"]=dict_key
dict[true]="mmmm"
for key,val in pairs(dict) do
    print(key,val)
end

    执行结果如下:

image-20220110120915378.png

    从执行结果可以看出,所谓以table、function为key,本质上还是取的table、function的地址;

    table还可以不用显示设置key,如下:

number_dict={"aaa","bbb","ccc"}
number_dict["2"]="ddd"
number_dict[5]="ffff"
number_dict["0"]="ddd"
for key,val in pairs(number_dict) do
    print(key,val)
end

for key,val in ipairs(number_dict) do
    print(key,val)
end

    执行结果如下:

image-20220110124453059.png

    可以看到,Lua中的table默认下标是从1开始的,并且遍历table可以用pairs和ipairs,但是区别在于ipairs只能遍历整型下标,遇到非整型的key就会遍历中断,相比之下pairs则不会受此限制;而ipairs可以保证遍历顺序。

function

        Lua的function十分灵活,和C/C++的方法相比,具有以下特点:

    (1)支持多返回值

    (2)支持可变参数列表

    (3)function是一种类型(可赋值、可作为参数传递,可存于变量和table,也可以被作为返回值返回)

    (4)局部函数和全局函数

    (5)function的作用域

function返回值

    Lua的function支持多返回值,如下图所示:

function multi_ret()
    local ret1="aaaa"
    local ret2="bbbb"
    return ret1,ret2
end

ret1,ret2=multi_ret()
print(ret1,ret2)

    若是不关注返回值,则可以用匿名变量接收返回值,如下:

function multi_ret()
    local ret1="aaaa"
    local ret2="bbbb"
    return ret1,ret2
end

_,ret2=multi_ret()
print(ret2)

function的形参

    对于Lua中的方法参数并没有类型,也不会进行类型检测。

    还有就是Lua的可变参数列表,有如下用法:

function multi_param(...)
    a=select("#",...)
    b=select(2,...)
    c=select(4,...)
    print(a,b,c)
end

multi_param(1,2,3)

    (1)通过接口select("#",...),用于获取形参的个数;

    (2)通过接口select(n,2),用于获取指定位置的形参,若指定位置形参不存在则返回nil。

    借助select和遍历器,可以做到依次获取参数:

function traverse_param(...)
    local args=select("#",...)
    for index=1,args do
        local arg = select(index,...)
        print(arg)
    end
end

traverse_param(1,2,3,4)

    如果有固定的参数,则需要放在可变形参前面,如下:

function partial_param(param1,...)
    print(param1)
    local argc = select('#',...)
    for index=1,argc do
        local la = select(index,...)
        print(la)
    end
end

partial_param(1,2,3,4)

函数的调用

    Lua的函数作为基本类型,也可以像number类型存于变量或者table中,如下:

tbl={}
function tbl:tbl_func1()
    print("this is func1",self)
end

    在调用的时候,可以通过冒号,也可以通过点号去调用,如下:

tbl={}
function tbl:tbl_func1()
    print("this is func1",self)
end

tbl.tbl_func1()
tbl:tbl_func1()

    区别在于,使用冒号调用的时候,会默认给一个self形参,指向调用者的地址。


作为基本类型的function

    作为基本类型的function,不仅可以像number类型一样存于变量或者table中;还可以作为函数参数、函数返回值进行传递,类似于C中的函数指针,如下所示:

function param_func(para1)
    print(para1)
end

function paramisfunc(para,func)
    func(para)
end

function  retisfunc()
    return (function (para)
               print("ret-func",para)
           end)
end
--函数可以存于变量中,并通过变量调用
a=param_func
a("var")
--函数可以存于table中
dict={}
dict["dict"]=param_func
for key,val in pairs(dict) do
    val(key)
end
--function 可以放在变量中作为参数进行传递
paramisfunc("func",a)

--function 也可以直接在参数列表内定义并传给形参
paramisfunc("func",(function(para1) 
                        print("param-func",para1)
                    end))
--function 返回值是一个方法
c=retisfunc()
c("777")

    在这里面需要注意的是,将function作为返回值时:

    (1)若是在return里面定义,则不需要定义函数名;

    (2)若是返回的function有参数,则这个参数是在接收这个返回值的对象在执行调用操作的时候指定的。

局部函数和全局函数

    函数定义的时候默认是全局的,这个时候函数名字到地址的映射关系存放在Lua的全局表_G里面。

    加上local关键字之后,则函数被定义为局部函数,这个局部函数分为两个层面,一个是在文件内的局部函数,一个是在函数内的局部函数。在文件内的局部函数:

    文件内的local function则是模块内可见,且需要注意的时候,文件内调用顺序遵循声明顺序,即,若是前面一个函数内调用到后面的一个local function,这种是会失败的,如下:

function param_func(para1)
    print(para1)
    test()
end

local function test()
    print("test")
end

param_func("test local")

    必须把test的定义放在param_func前面才行,通过下图辅助说明:

image-20220111104229917.png


    这个原因是对于Lua来说,作用域是从定义开始有效(function和local function都是一样);但是在实现层面二者原理是不同的,这里简要说一下:

    1)对于local function来说:Lua代码分为代码生成和执行两个阶段,Lua的变量分为全局变量、upvalue和运行时上下文变量,分别存于全局表,upvalue表和local表中,在代码生成阶段的时候,遇到函数调用操作Lua解释器会按照Local表->upvalue表进行查找,找到的话直接在生成代码的时候给到对应表的一个下标(不会给地址,因为地址可能会在运行时被改变),若是找不到则直接生成去全局表查找的指令,而在当前函数后定义的function,则必然是找不到的外层的local表和upvalue表没有,所以最终是去全局表里面找,必然找不到,在执行时出错。

    2)对于global function来说,全局表是在执行过程中被填充的,Lua文件中的function func()语句,实际上等价于func = function() ... end,最终效果是在全局表里面加一个key/val pair,所以在执行过程中调用后面的function时,实际全局表没有对应key/val pairs,所以会报错。

    由此可以得到下面的结论:

    (1)local function的作用域是定义位置之后代码可见;

    (2)对于全局函数,在同一个Lua文件中,也是得先定义后使用。


    local function的封装性提供:

function param_func(para1)
    print(para1)
    local function local_param_func(para1)
        print("local param func",para1)
    end
    local_param_func(para1)
    a=local_param_func
    return a
end

param_func("inner call")
--local_param_func("outer call")
ret=param_func
ret("ret_call2")

    上述示例展示的是在function param_func里面定义了一个局部函数local_param_func,因为用local进行修饰,所以这个接口只能在param_func内可见,并不能被外部直接调用(但是可以通过return语句将local函数返回出来,供外部接口调用)。

    Lua local function的可见性可以用来模拟类似C++这种面向对象语言的类成员方法的封装性,对于不希望被外部调用的接口,在函数内声明为local function。

upvalues

    Lua中,函数内可以定义函数,函数内定义的函数可以使用外层函数定义的局部变量,如下:

function global_test()
    local var1=1
    local var2=2
    local function local_test_1()
        var1=var1+1
        var2=var2+1
        print(var1,var2)
    end

    function global_test_2()
        var1=var1+1
        var2=var2+1
        print(var1,var2)
    end
    print(var1,var2)
    local_test_1()
    global_test_2()
    return local_test_1
end

global_test()

    如果function里面定义多个function,无论是local还是global,其值是共享的。

    除此之外,若是把function作为返回值传递回来,每次调用若其局部变量发生改变,则这种改变是可以保持的,如下:

function global_test()
    local var1=1
    local var2=2
    local function local_test_1()
        var1=var1+1
        var2=var2+1
        print(var1,var2)
    end

    function global_test_2()
        var1=var1+1
        var2=var2+1
        print(var1,var2)
    end
    print(var1,var2)
    local_test_1()
    global_test_2()
    return local_test_1
end

ret=global_test()
ret()
ret()

    上面这些,是Lua借助upvalue机制做到的:

    所谓upvalue,是Lua模拟实现,类似C语言的静态变量的一种机制;一个带上upvalue的函数,我们称之为闭包,那么什么是upvalue呢?在本例中:

    1)global_test有两个局部变量,var1和var2;里面定义了一个函数local_test_1,引用了外部的var1和var2,则我们称var1和var2是local_test_1的upvalue;同理var1和var2也是global_test_2()的upvalue;

    2)带有upvalue的function就是一个闭包,因此local_test_1()是一个闭包,global_test_2()也是一个闭包。

    闭包里面upvalue的值是可保持的,一旦一个闭包被创建了,其里面的upvalue的生命周期同这个闭包;且闭包之间的upvalue的值互不影响。


参考文件

    构建Lua解释器Part11:Upvalue - 知乎 (zhihu.com)



相关文章

关于LUA(上)

    Lua是一种轻量小巧的脚本语言,C语言编写,并提供了易于使用的扩展接口和机制,易于嵌入到应用中,在游戏开发中经常被用来进行外层业务系统的开发。Lua的table    Lua的基本数据类型有八种,分别是:nil、boolean、number、string、userdata、function、thread 和 t...

关于LUA(下)

关于LUA(下)

Lua与OOP    Lua是面向过程的语言,不提供面向对象的特性,但是我们可以利用Lua的元表和元方法模拟面向对象的效果。OOP的特性封装    所谓封装,是隐藏对象的属性和细节,仅对外暴露公共的访问方式。本质上分为两层:    1)成员变量和成员方法,提升代码的内聚性,降低模...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。