Lua 要点精选

  序言:一直专注C#开发,Lua算是作为第二语言吧,需要全面的基本的了解一下。下述案例自己跟着敲了一遍,还有补充一些自己感到疑惑的。介绍了lua 重点部分。

一 Lua 介绍

    Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放。

    设计目的:嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

      特性:

          ①轻量级,用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入到别的程序里。

          ②可扩展性,Lua提供了非常易于使用的扩展和机制:由宿主语言(C/C++)提供的这些功能,Lua可以使用它们,就像本来就内置的功能一样。

     其他特性:

         ①支持面向过程和函数式编程。

         ②自动内存管理:只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;

         ③语言内置模式匹配;闭包;函数也可以看做一个值;提供多线程(协同程序,并非操作系统所支持的线程)支持。

         ④通过闭包和table可以很方便的支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。

    Lua应用场景:

         游戏开发

         独立应用脚本

         Web应用脚本

         扩展和数据库插件

         安全系统,如入侵检测系统

二 环境安装

    链接:https://pan.baidu.com/s/1S3rHjW6YOMJbPgGvMVjHlA
    提取码:owiv

    下载这个上述资源,之后根据这个步骤:https://blog.csdn.net/wue1206/article/details/81463503,亲测可用。

三 数据类型

    Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。值可以存储在变量中,作为参数传递或结果返回。

    nil,表示无效值。

      I.表示删除一个值。

      

      II.作比较的时候,应该加上双引号,type(X)=="nil",原理可以看如下案例。

      

      III.nil可以当作boolean中false.

    boolean,

      boolean类型只有两个可选值:true(真)false(假),Lua把false和nil看作“假”,其他为“真”。

    number,双精度类型的实浮点数   

    string,由一对双引号或单引号,或者[[]].

        ①在对一个数字字符串进行算术操作时,Lua会尝试将这个数字字符串转成一个数字

          print("2"+"3") -- 5.0

          print("2+3") -- 2+3

        ②#计算字符串长度,放在字符串前面

          #"shshshd" --7

    function,由C或Lua编写的函数

        ①函数式被看作是“第一类值”,函数可以存在变量里

    userdata,表示任意存储在变量中的C数据结构

        一种用户自定义数据,用于表示一种由应用程序或C/C++语言库所创建的类型,可以将任意C/C++的任意数据类型的数据(通常是struct和指针)存储到Lua变量中调用。

    thread,在lua里最主要的线程是协同程序(coroutine)。他跟线程差不多,拥有自己独立的栈,局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。

        线程和协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时,才会暂停。

    table,

      ①Lua中的表(table),其实是一个“关联数组”,数组的索引可以使数字,字符串。

      ②在Lua里,table的创建是通过“构造表达式”来完成。最简单的单构造表达式是{},用来创建一个空表。

      ③不同于其他语言的数组,Lua默认初始索引是1开始。

      ④table长度不会固定大小,有新数据添加table长度会自动增长,没初始化的table都是nil

        

      

四 变量

    Lua中的变量全是局部变量,哪怕是语句块或函数中,除非用local显式声明为局部变量。局部变量的作用域为从声明位置开始到所在语句块结束。

变量的默认值均为nil。   

    

    赋值语句

        Lua可以多个变量同时赋值,变量列表和值列表的各个元素用逗号隔开,赋值语句右边的值会依次赋值给左边的变量。多值赋值经常用来交换,或将函数调用返回给变量

          

        遇到赋值语句Lua会先计算右边的值,然后再执行赋值操作,所以我们可以这样交换变量的值。以下是2个值和2个值赋值

          

        当变量个数和值不一致时,不足则补nil,多余则忽略。

    索引

        对table的索引使用方括号[]。Lua也提供了 . 点操作符。

        t[i] 或 t.i

        

 

五 循环 

    ①while循环    

 

while(condition) do
    statements
end

    ②for循环  

        I.for do

for i =1,10 do
    print(i)
end

         II.for k,v in pairs(tab) do

       适用于对象

         III.for k,v in ipairs(tab) do

        相对上述方案,限制较多:

          k 只能是整型,且必须顺序+1递增,否则之后的不再遍历。

          如果v 为nil ,则也停止执行。

        适用于数组    

local ret = {}

ret[1] = nil
ret[2] = "nil"

for k,v in ipairs(ret) do
    print(k.."|"..v)
end   

     ③repeat...until

       重复执行,直到指定的条件为真(与while循环相反,不知道这个存在的意义是啥)

local i = 0;
repeat
    i=i+1
    print(i)
until(i>5)

六 流程控制

复制代码
local i = 0
if(i==0) then
    print("if data")
elseif(i==1) then
    print("elseif data")
elseif(i==2) then
    print("elseif data")
else
    print("else data")
end
复制代码

七 函数

    ①Lua编程中,经常遇到函数的定义好调用。我们有两种函数定义和调用的方法。一种是用属性的定义,另外一种是通过冒号的形式(其实也是属性)。只不过用冒号形式声明的函数默认有一个参数self。self指向调用者本身。

    单个点的调用。

    

    改成下述写法就对了  

Animal ={id = 2,name = "djw"}

function Animal:PrintLog()
    print(self.id)
end

Animal.PrintLog(Animal) 
Animal:PrintLog()--写冒号,代表默认第一个参数传递本身

    还可以这么写

Animal ={id = 2,name = "djw"}

function Animal.PrintLog(tab)
    print(tab.id)
end

Animal:PrintLog()--写冒号,代表默认第一个参数传递本身

 

      ②在调用函数时,也需要将对应的参数放在一对圆括号里,即使调用函数时没有参数,也必须写一对空括号。对于这个规则只有一种特殊的例外:一个函数若只有一个参数,并且此参数是一个字符串或table的构造是,那么圆括号便可以省略掉。

复制代码
print "hellow"
print[[helllllll]]

function f(tab)
    for k,v in pairs(tab) do
        print(k.."|"..v)
    end
end

f({x=1,y=2})
f{x=3,y=10}
复制代码

 

复制代码
function foo0() end                         -- 无返回值
function foo1() return "a" end          -- 返回一个结果
function foo2() return "a", "b" end     -- 返回两个结果

-- 在多重赋值时,如果一个函数调用是最后,或仅有的一个表达式,
-- 那么Lua会保留其尽可能多的返回值,用于匹配赋值变量
x, y = foo2()               -- x = "a", y = "b"
x = foo2()                    -- x = "a", "b"被丢弃
x, y, z = 10, foo2()     -- x = 10, y = "a", z = "b"

-- 如果一个函数没有返回值或者没有足够多的返回值,那么Lua会用
-- nil来补充缺失的值
x, y = foo0()               -- x = nil, y = nil
x, y = foo1()               -- x = "a", y = nil
x, y, z = foo2()          -- x = "a", y = "b", z = nil

-- 如果一个函数调用不是一系列表达式的最后一个元素,那么将只产生一个值: !!!!!!!!!!
x, y = foo2(), 20          -- x = "a", y = 20
x, y = foo0(), 20, 30     -- x = nil, y = 20, 30则被丢弃

-- table构造式可以完整的接收一个函数调用的所有结果,即不会有任何数量
-- 方面的调整
local t = {foo0()}          -- t = {}(一个空的table)
local t = {foo1()}          -- t = {"a"}
local t = {foo2()}          -- t = {"a", "b"}

-- 但是,对于上述的行为,只有当一个函数调用作为最后一个元素时才会发生,
-- 而在其他位置上的函数调用总是只产生一个结果值
local t = {foo0(), foo2(), 4}          -- t[1] = nil, t[2] = "a", t[3] = 4

-- 我们也可以在一个函数中,使用return返回另一个函数的返回值
function MyFunc()          -- 返回a
     return foo1()          -- 注:这里是return foo1(),而不是return (foo1())
end

-- return foo1()和return (foo1())是两个完全不同的意思
-- 将一个函数调用放入一对圆括号中,从而迫使它只返回一个结果!!!!!!!!!!!!
print((foo0()))          -- nil
print((foo1()))          -- a
print((foo2()))          -- a
复制代码

 

    ①多返回值,可以返回多个结果值   

复制代码
function max(num1,num2)
    if(num1>num2) then
        return num1
    end

    return num2,num1,num2
end

print(max(10,2))
复制代码

    ②可变参数,可以接受可变数目的参数,和C语言类似,在函数列表中使用三个点 ... 表示函数有可变的参数。注意{...}代表变长参数构成的数组

function test(...)
    print("可变参数长度为:"..select("#",...))
    for k,v in pairs({...}) do
        print(k..v)
    end
end

test(1,3,4,"ss")

    可以通过select("#",...) 获取可变参数的数量

    还可以通过select(n,...),用于返回n到select("#",...)的参数

八 迭代器

    迭代器(iterator)是一种对象,他能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。

在Lua中迭代器是一种支持指针类型的结构,他可以遍历集合的每一个元素。

    泛型for迭代器,泛型for在自己内部保存迭代函数,实际上它保存三个值:迭代函数,状态常量,控制变量。

local array = {"aaaa","bbbb"}
for k,v in ipairs(array) do
    print(k..v)
end

    泛型for执行过程

      ①初始化,计算in后面的表达式的值,表达式应该返回泛型for需要的三个值:迭代函数,状态常量,控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会忽略。

      ②将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)

      ③将迭代函数返回的值赋给变量列表

      ④如果返回的第一个值为nil循环结束,否则执行循环体

      ⑤回到第二步调用迭代函数

    分两种迭代器

      无状态的迭代器,指的是不保留任何状态的迭代器,因此我们在循环中可以利用无状态迭代器避免创建闭包花费的额外的代价。每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用。一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态的迭代器的典型的简单例子就是ipairs,它遍历数组的每一个元素。迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标,ipairs和迭代函数都很简单,我们在Lua中可以这么实现:

      

      多状态的迭代器,很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法就是使用闭包。还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数了。   

复制代码
function cpairs(arr)
    local index = 0
    local count =#arr
    return function ()
        index =index +1
        if(index <=count) then
            return arr[index]
        end
    end

end

local arr = {"djw","dll"}
for v in cpairs(arr) do
    print(v)
end
复制代码

九 table

  table是Lua的一种数据结构用来帮助我们创建不同的数据类型,如数组,字典等。

  Lua table使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是nil。

  Lua table是不固定大小的,你可以根据自己需要来进行扩容。

  Lua也是通过table来解决模块(module),包(package)和对象(Object)的。

  

  table的构造

    构造器是创建和初始化表的表达式。表示Lua特有的功能强大的东西。最简单的是构造函数{},用来创建一个空表。直接可以初始化数组。

    mytable ={}

    mytable[2]="lua"

    mytable = nil -- 移除引用,垃圾回收会释放内存

    当我们为table a并设置元素,然后将a赋值给b,则a与b都指向同一个内存。如果a设置为nil,则b同样能访问table的元素。如果没有指定的变量指向a,lua的垃圾回收机制会清理相对应的内存。

  table的操作

    table.concat(table,sep,start,end),concat是concatenate(连锁,链接)的缩写,table.concat()函数列出函数中指定table的数组部分从start位置到end位置的所有元素,元素间以指定的分隔符sep隔开

    table.insert(table,pos,value),在table的数组部分指定位置pos插入值为value的一个元素,pos参数可选,默认为数组部分末尾

    table.remove,返回table数组部分位于pos位置的元素,其后的元素会被前移,pos参数可选,默认为table的长度,即从最后一个元素删起。

    table.sort,对给定的数组进行正序排序

    注意:当我们在获取table的长度的时候,无论是使用#还是table.getn,其都会在索引中断的地方停止计数,而导致无法正确取得table的长度。

可以使用下述方法来代替: 

function getlength(tab)
    int index  = 0
    for k,v in pairs(tab) do
        index = index +1
    end
    return index
end

十 模块与包

  模块类似于一个封装库,Lua加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以API的接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

  Lua的模块是由变量函数等已知元素组成的table,因此创建一个模块很简单,就是创建一个table,然后把需要导出的常量,函数放入其中,最后返回这个table就行。 

复制代码
module = {}
module.costant = "常量"
function module.func1()
    print("func1")
end

function module.func2()
    print("func2")
end

local function func3()
    print("local func3")
end

return module
复制代码

    由上可知,模块的机构就是一个table的结构,因此可以像操作table里的元素那样来操作调用模块里的常量或函数。

   require函数

      Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单的调用就可以了。

        require("<模块名>") require"<模块名>"

      执行require后会返回一个由模块常量或函数组成的table,并且还会定义一个包含该table的全局变量。

      注意:require的文件所在路径

  加载机制(这段没明白)

      由于自定义的模块,模块文件不是放在哪个文件目录都行,函数require有它自己的文件路劲加载策略,他会尝试从Lua文件或C程序库中加载模块。

      require用于搜索Lua文件的路劲是存放在全局变量package.path中,当Lua启动后,会以环境变量LUA_PATH的值来初始化这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。

      如果找到目标文件,则会调用package.loadfile来加载模块。否则,就会去C程序库找。

      搜索的文件路径是从全局变量package.cpath获取,而这个变量则是通过环境变量LUA_CPATH来初始。

      搜索的策略跟上面一样,只不过现在换成搜索的是so或dll类型的文件。如果找到,那么require就会通过package.loadlib来加载它。

  C包(这段没明白)

      Lua和C是很容易结合的,shiyongC为Lua写包。

      与Lua中写包不同,C包在使用以前必须先加载并连接,在大多数系统中最容易得实现方式是通过动态链接库机制。

      Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数,库的绝对路径和初始化函数。  

local path = "/user/local/lua/lib/libluasocket.so"
local f = loadlib(path,"luaopen_socket")

      loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反而他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用它。

      如果动态加载库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:   

local path = "/user/local/lua/lib/libluasocket.so"
local f = assert(loadlib(path,"luaopen_socket"))
f() --真正打开库

十一 元表

    在Lua table中我们可以访问对应的key来得到value值,但是却无法对两个table在进行操作。

    为此,lua提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。

    ①__add,lua计算两个table的相加操作

      当lua试图对两个表进行相加时,先检查两者之一知否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"即字段,其对应的值(往往是一个函数或是table)就是"元方法"。

      有两个重要的函数处理元表:

                   setmetatable(table,metatable):对指定table设置元表(metatable),如果元表(metatable)中存在__metatable健值,setmetatable会失败。

                   getmetatable(table):返回对象的元素(metatable)           

ret = {}
meta = {}
setmetatable(ret,meta)
print(getmetatable(ret))

--ret2 = setmetatable({},{}) 另外一种写法

      ②__index 

        这是metatable最常用的健。

        当你通过健来访问table的时候,如果这个健没有值,那么lua就会寻找该table的metatable(假设有metatable)中的__index健。如果__index包含一个表格,Lua会在表格中查找相应的健

        如果__index包含一个函数的话,lua就会调用那个函数,table和健会作为参数传递给函数。__index元方法查看表中元素是否存在,如果不存在,返回结果为nil;如果存在,则由__index返回结果。

        

          lua查找一个表元素时的规则:

                      I.在表中查找,如果找到,返回该元素,找不到则继续

                      II.判断该表中是否有元表,如果没有元表,则返回nil,有元表则继续

                      III.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__idex 方法是一个表,则重复I,II,III;如果__index方法是一个函数,则返回该函数的返回值。

       ③__newindex  

         用来对表更新。

         当你给表的一个缺少的索引赋值,解释器就会查找__newindex元方法;如果存在则调用这个函数而不进行赋值操作。

      

        以上实例中表设置了元方法__newindex,在对新索引(newKey)赋值时,会调用元方法,而不进行赋值。而如果对已存在的索引健(key1),则会进行赋值,而不调用元方法__newindex

      

      ④为表添加操作符

        

        

 

      ⑤__call元方法

        __call元方法在lua调用一个值时调用。

        

      ⑥__tostring 元方法

        __tostring用于修改表的输出行为。

        

 十二 协同程序

  lua协同程序(coroutine)与线程类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其他协同程序共享全局变量和其他大部分东西。

  协同和线程的区别:一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任意时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的要求被挂起的时候才会被挂起。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

  

   

  coroutine.running就可以看出来,coroutine在底层实现的就是一个线程。

   当create一个coroutine的时候就是在新线程中注册了一个事件。

  当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。

  生产者-消费者问题        

复制代码
local newProductor

function productor()
    local i = 0
    while true do
        i = i+1
        send(i)
    end
end

function consumer()
    while(true) do
        local i =receive()
        print(i)
    end
end

function receive()
    local status,value = coroutine.resume(newProductor)
    return value
end

function send(x)
    coroutine.yield(x)
end

newProductor = coroutine.create(productor)
consumer()
复制代码

 十三 垃圾回收

  lua采用了自动内存管理。意味着你不用操心新创建的对象需要的内存是如何分配出来,也不用考虑在对象不再被使用后怎么样释放所占用的内存。

  lua运行了一个垃圾收集器来收集所有死对象(即Lua不可能访问到的对象)来完成自动管理内存管理的工作。lua中所用到的内存:字符串,表,函数,线程等都服从内部管理。

  lua实现了一个增量标记-扫描器。它使用两个数字来控制垃圾收集循环:(都使用百分数为单位,100在内部表示1)

          ①间歇率:控制着垃圾收集器需要开启新的循环前要等待多久。增大这个值会减少收集器的积极性。当这个值比100小的时候,收集器在新的循环前不会有等待。设置这个值为200就会让收集器等到总内存使用量达到之前的两倍时才开始新的循环。

          ②步进倍率:控制着收集器运作速度相对于内存分配速度的倍率。增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。不要把这个值设置小于100,那样的话,收集器工作太慢了以至于永远都干不完一个循环。默认值是200,这表示收集器以内存分配的“两倍”速度工作。

十四.面向对象

  I.封装,指能够把一个实体的信息,功能,响应都装入一个单独的对象中的特性。

  II.继承,继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。

  III.多态,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

  IV.抽象,抽象是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的集成级别解释问题。

  我们知道对象由属性和方法组成。lua中最基本的结构是table,所以需要用table来描述对象的属性。

  lua中的function可以用来表示方法。那么lua中的类可以通过table+function模拟出来。至于继承,可以通过metetable模拟出来。

  lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值得对象(table)代表两个不同的对象;一个对象在不同的时候也可以有两个不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建,在哪创建没有关系。对象有他们的成员函数,表也有。 

①创建对象,主要使用__index元表实现

复制代码
people = {id = 0,name = ""}

function people:new(i,n)
    local o =  {}
    setmetatable(o,self)
    self.__index = self
    self.id = i
    self.name = n
    return o
end

function people:PrintSelf()
    print("id:"..self.id.."name:"..self.name)
end

local djw = people:new(1000,"djw")
djw:PrintSelf()
djw.PrintSelf(djw)--同上一行一致,写法不同而已,参考函数章节

local xiaohong = people:new(1001,"xiaohong")
xiaohong:PrintSelf()
xiaohong.PrintSelf(xiaohong)
复制代码

 ②继承,指一个对象直接使用另外一个对象的属性和方法。可用于扩展基础类的属性和方法。

复制代码
WoMan =people:new()
function WoMan:new(i,n)
    o = people:new(i,n)
    setmetatable(o,self)
    self.__index = self
    return o
end

function WoMan:TestOne()
    print(self.id.."|||"..self.name)
end

local xiaohong = WoMan:new(10,"xiaohong")
xiaohong:TestOne()
xiaohong:PrintSelf()
复制代码

 

参考:https://www.runoob.com/lua/lua-tutorial.html

   https://www.cnblogs.com/ring1992/p/6000731.html

   https://www.cnblogs.com/ring1992/p/6000893.html

posted @   不三周助  阅读(585)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?

喜欢请打赏

扫描二维码打赏

了解更多

点击右上角即可分享
微信分享提示