Lua快速入门笔记

文章目录

  • Lua快速入门笔记
    • 前言
    • 1、Lua概述
    • 2、Lua环境安装
    • 3、快速体验Lua编程
    • 4、数据类型
    • 5、变量
    • 6、循环
    • 7、流程控制
    • 8、函数
    • 9、运算符
    • 10、字符串
    • 11、数组
    • 12、迭代器
    • 13、表
    • 14、模块与包
    • 15、元表
    • 16、协同程序

Lua快速入门笔记

image-20230204150824404

前言

本文是笔者参考菜鸟教程对Lua的一个快速入门学习,主要是快速了解Lua的基本语法,让我能够看懂并编写一些简单的Lua脚本。我学习Lua的动机,是在学习Redis时,在高并发的情况下,为了防止出现超卖问题,我需要保证一个线程获取锁和释放锁的操作具有原子性,这个问题的解决方案有很多种方式,但是我选择使用Lua(明确来讲是老师教的Lua脚本方案),所以就趁机花2~3小时快速学习一下Lua,而且Lua也比较有用,就算现在不学,以后也是需要学习的,还不如趁着现在有时间抓紧学一下😄

学习站点:Lua 环境安装 | 菜鸟教程 (runoob.com)

1、Lua概述

  • Lua是什么

    Lua 是一个小巧的脚本语言,是一种动态解释型语言,它由C编写,可以很容易和C\C++代码进行相互调用。

    Lua官网:Lua

  • Lua是用来干什么的

    • 游戏开发:Lua 是一种被广泛用于游戏开发中的一种计算机语言,方便开发者定制自己所需的功能。其中,红遍全球的《愤怒的小鸟》就是由 Lua 语言用 Wax 开发的。此外,梦幻西游、奇迹暖暖、开心消消乐、放置奇兵、最强蜗牛等手游也采用了 Lua 语言进行编写。
    • 嵌入式开发:ngx_lua,迅雷的blot界面引擎,WOW的脚本引擎
    • 提高效率,可以编写一些有用的小巧工具,解放你的双手,比如开发一个”外挂“让你游戏挂机刷经验
    • 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
    • 安全系统,如入侵检测系统
  • 设计Lua的主要的目的

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

  • Lua的特点

    • 开源免费。Github上Lua的开源仓库:Lua的开源仓库
    • 轻量级。Lua语言的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快,从而适合嵌入在别的程序里。5.0.2版的Lua的内核小于120KB,而Python的内核大约860KB,Perl的内核大约1.1MB
    • 可扩展。Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样
    • 可嵌入。Lua是一个快速的语言引擎,占用空间小 您可以轻松地嵌入到您的应用程序中
    • 支持面向过程函数式编程(procedure-oriented programming)和函数式编程(functional programming)
    • 自动内存管理。Lua只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象
    • 高效。Lua是目前速度最快的脚本语言之一
  • Lua名字的由来

    Lua (发音为LOO-ah) 在葡萄牙语中的意思是“月亮”。 因此,它既不是首字母缩略词也不是缩写,而是名词。 更具体地说, “Lua”是一个名字, 地球月球的名称和语言的名称。 像大多数名字一样, 它应该以小写字母写,首字母大写,即“Lua”。 请不要将其写成“LUA”, 这既丑陋又令人困惑, 因为那时它变成了首字母缩略词 对不同的人有不同的含义。 所以,请正确写“Lua”!

    ————引自Lua官方文档

  • Lua的历史

    它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。 ————引自百度

    推荐阅读:Lua诞生-Lua历史-Lua作者

2、Lua环境安装

这里我只演示在Linux上安装,至于Windows的安装可以参考菜鸟教程。Linux中常见的有四种安装软件的方式,我使用的是第四种,即源码编译安装。

  • Step1:安装curl

    Linux 系统中的 curl 命令是一个利用 URL 在命令行工作的文件传输工具,通常用于 Linux 系统中服务的访问和文件的下载。curl 命令支持HTTP、HTTPS、FTP等多种协议,可用于服务请求的模拟和文件的上传下载。并不是所有的系统中都会自带 curl 命令,对于简洁版的 Linux 系统可能并不存在(我的Linux系统就没有自带curl)

    yum install curl
    
  • Step2:下载Lua源文件

    在下载前我们需要线使用cd命令切换到安装目录,Linux中安装目录一般是/usr/local/目录,而我是下载在/usr/local/src/目录下

    curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz
    
  • Step3:解压Lua源文件

    # 解压Lua源文件
    tar zxf lua-5.4.4.tar.gz
    # 删除Lua源文件
    rm -rf lua-5.4.4.tar.gz
    # 重命名Lua
    mv lua-5.4.4 lua
    
  • Step4:编译并安装

    # 进入Lua的安装目录
    cd lua
    # 编译并安装Lua
    make linux && sudo make install
    # 备注:如果当前是处于root用户,则sudo命令可以省略
    
  • Step5:测试。编写第一个lua脚本

    # 编写hello.lua文件
    vim hello.lua
    # 写入以下内容,然后保存退出
    print("Hello World!")
    # 执行程序
    lua hello.lua
    

    image-20230204161106702

    出现上面那个结果,就说明Lua环境安装

3、快速体验Lua编程

Lua编程分为两种:交互式编程、脚本式编程

  • 交互式编程

    使用lua -ilua命令开启lua编程命令窗口

    image-20230204162201979

    这种方式,能够快速编程并执行,但是有一个致命的缺点:那就是只能输入,不能删除和移动光标

  • 脚本式编程

    在 Lua环境安装 时,就已经演示过了,主要步骤就算,先创建一个文件,文件的后缀名是.lua,然后保存退出,然后就可以使用lua fileName.lua直接运行lua脚本了

4、数据类型

Lua 和 JS 一样是动态类型语言,变量不要类型定义,只需要为变量赋值。值可以存储在变量中,作为参数传递返回结果。Lua中有8中基本数据类型:

image-20230204163339410

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
print(type(type(X)))            --> string

注意点

  1. 当变量没有声明时,就是nil

  2. nil作比较时,需要加上双引号,因为type函数返回的是一个字符串>

  3. Lua 把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true

  4. 在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里

    -- 一个斐波那契函数
    function factorial1(n)if n == 0 thenreturn 1elsereturn n * factorial1(n - 1)end
    end
    print(factorial1(5))
    factorial2 = factorial1
    print(factorial2(5))
    

    image-20230204170454949

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

  6. 在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停

  7. 在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表(这个类似于Java中的Map集合,也有点像Python中的字典 Dict)

    a = {}
    a["key"] = "value"
    key = 10
    a[key] = 22
    a[key] = a[key] + 11
    for k, v in pairs(a) doprint(k .. " : " .. v)
    end
    

    image-20230204164553860

    其它语言一般是以0作为起始索引,但是lua中是将1作为起始索引

    tbl = {"apple", "pear", "orange", "grape"}
    for key, val in pairs(tbl) doprint("Key=", key,"Value=",val)
    end-- lua中不仅可以通过[index]获取值,还可以通过 表名.index获取值
    print(tbl.1) -- apple
    

    image-20230204165020727

5、变量

  • 变量的作用域

    作用域的定义:Lua 中的一个变量在程序中都有一定的作用范围,我们称之为作用域。

    在Lua中,Lua 变量有三种类型:全局变量、局部变量、表中的域。使用变量前都需要先进行声明,全局变量是直接就使用,而局部变量需要通过local关键字声明,

    -- test.lua 文件脚本
    a = 5               -- 全局变量
    local b = 5         -- 局部变量function joke()c = 5           -- 全局变量local d = 6     -- 局部变量
    endjoke()
    print(c,d)          --> 5 nildolocal a = 6     -- 局部变量b = 6           -- 对局部变量重新赋值print(a,b);     --> 6 6
    endprint(a,b)      --> 5 6
    
  • 变量的赋值

    在Lua中,不仅支持直接的单个赋值,还支持一次赋值多个(类似于ES6中的结构赋值)

    a = 1a, b, c = 0, 1
    print(a,b,c)             --> 0   1   nila, b = a+1, b+1, b+2
    print(a,b)               --> 1   2a, b, c = 0
    print(a,b,c)             --> 0   nil   nil-- 交换x、y的值
    x, y = y, x-- 接收函数的多个返回值
    a, b = f()  -- 函数f的第一个返回值给a,第二个返回值给b
    

    批量赋值主要遵循以下规则:

    a. 变量个数 > 值的个数             按变量个数补足nil
    b. 变量个数 < 值的个数             多余的值会被忽略
    

6、循环

Lua 语言提供了以下几种循环处理方式:

image-20230204174335749

Lua中的循环控制语句:

image-20230204174421727

  • while循环

    while(condition)
    dostatements
    end
    

    备注statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句

  • for循环

    for var=exp1,exp2,exp3 do  <执行体>  
    end  
    

    var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 “执行体”。exp3 是可选的,如果不指定,默认为1。

    PS:这个和Java中的for循环类似的😄,感觉学懂一门语言再去看其他语言发现基础语法都差不多

    • 数值for循环

      function f(x)  print("function")  return x*2  
      end for i=1,f(1) doprint(i)  --> function 1 2
      endfor i=10,1,-2 doprint(i) --> 10 8 6 4 2
      end
      

      ==注意:==for的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(1)只会在循环开始前执行一次,其结果用在后面的循环中

    • 泛型for循环

      泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句

      days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}  
      for i,v in ipairs(days) do  print(v) --> 轮流打印表中的数据
      end 
      
  • repeat...util()

    Lua 编程语言中 repeat…until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat…until 循环的条件语句在当前循环结束后判断(类似于Java中do...while

    repeatstatements
    until( condition )
    
    --[ 变量定义 --]
    a = 10
    --[ 执行循环 --]
    repeatprint("a的值为:", a)   --> 10 11 12 13 14 15a = a + 1
    until( a > 15 )
    

7、流程控制

Lua提供以下流程控制的结构语句:

image-20230204182433110

  • if

    if(布尔表达式)
    then--[ 在布尔表达式为 true 时执行的语句 --]
    end
    
  • if...else...

    if(布尔表达式)
    then--[ 布尔表达式为 true 时执行该语句块 --]
    else--[ 布尔表达式为 false 时执行该语句块 --]
    end
    
  • if嵌套

    if( 布尔表达式 1)
    then--[ 布尔表达式 1 为 true 时执行该语句块 --]if(布尔表达式 2)then--[ 布尔表达式 2 为 true 时执行该语句块 --]end
    end
    

8、函数

  • 函数的定义:函数是对语句和表达式进行抽象的主要方法

Lua中函数分为两大类:

  • 内置函数:内置函数是Lua官方已经写好的函数,比如,print()函数
  • 自定义函数:是开发者自己定义的用于完成指定功能的函数,需要使用function关键字进行声明

Lua中函数主要有两种功能:

  • 完成指定的任务,这种情况下函数作为调用语句使用
  • 计算并返回值,这种情况下函数作为赋值语句的表达式使用

函数声明语法:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)function_bodyreturn result_params_comma_separated
end
  • optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
  • function_name: 指定函数名称。
  • argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
  • function_body: 函数体,函数中需要执行的代码语句块。
  • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。

示例1:可以使用类似于解构赋值的方式获取函数的多个返回值

s, e = string.find("www.runoob.com", "runoob") 
print(s, e)  --> 5    10

示例2:可以向函数传可变参数

function add(...)  
local s = 0  for i, v in ipairs{...} do   --> 将可变参数当作 exp3s = s + v  end  return s  
end  
print(add(3,4,5,6,7))  --->25

示例3:可以通过 #tableName 来获取可变参数的个数

function average(...)result = 0local arg={...}    --> 使用表存储传进来的可变参数,然后赋值给argfor i,v in ipairs(arg) doresult = result + vendprint("总共传入 " .. #arg .. " 个数")  --> 通过 #arg 获取可变参数的个数return result/#arg
endprint("平均值为",average(10,5,3,4,5,6))

image-20230204184443288

示例4:可以通过select("#",...) 来获取可变参数的数量

function average(...)result = 0local arg={...}for i,v in ipairs(arg) doresult = result + vendprint("总共传入 " .. select("#",...) .. " 个数")return result/select("#",...)
endprint("平均值为",average(10,5,3,4,5,6))

image-20230204184342136

注意:如果我们想要几个固定参数,则固定参数必须卸载可变参数的前面(也就是写在...的前面,这个和Java中的可变参数一致)

示例5:通过使用select(x,...)截取可变参数从x到n(n是可变参数的最后一个数)的数

取值范围相当于[x,n],这个类似于Java中这个APIsubstring(int x) ,只是这个API的范围是 (x,n]

function f(...)a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数print (a)print (select(3,...)) -->打印所有列表参数
endf(0,1,2,3,4,5)

image-20230204185126378

9、运算符

运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:

设定 A 的值为10,B 的值为 20

  • 算术运算符

    image-20230204185558032

  • 关系运算符

    image-20230204185713834

  • 逻辑运算符

    image-20230204185755913

  • 其他运算符

    image-20230204185808792

  • 运算符的优先级

    优先级顺序为:从上(优先级)到下(优先级)

    ^
    not    - (unary)
    *      /       %
    +      -
    ..
    <      >      <=     >=     ~=     ==
    and
    or
    

    注意:除了 ^.. 外所有的二元运算符1都是左连接2

    a+i < b/2+1          <-->       (a+i) < ((b/2)+1)
    5+x^2*8              <-->       5+((x^2)*8)
    a < y and y <= z     <-->       (a < y) and (y <= z)
    -x^2                 <-->       -(x^2)
    x^y^z                <-->       x^(y^z)
    

10、字符串

  • 字符串的定义:字符串或串(String)是由数字、字母、下划线组成的一串字符。

Lua 语言中字符串可以使用以下三种方式来表示:

  • 单引号间的一串字符。
  • 双引号间的一串字符。
  • [[]] 间的一串字符。
string1 = "Lua"
print("\"字符串 1 是\"",string1)  --> "字符串 1 是" string2 = 'runoob.com'
print("字符串 2 是",string2) --> 字符串 2 是    runoob.comstring3 = [["Lua 教程"]]
print("字符串 3 是",string3)  --> 字符串 3 是    "Lua 教程"

常见的字符串操作

API作用
string.upper(argument)字符串全部转为大写字母
string.lower(argument)字符串全部转为小写字母
string.gsub(mainString,findString,replaceString,num)在字符串中替换。mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换)
string.find (str, substr, [init, [plain]])在一个指定的目标字符串 str 中搜索指定的内容 substr,如果找到了一个匹配的子串,就会返回这个子串的起始索引和结束索引,不存在则返回 nil
init 指定了搜索的起始位置,默认为 1,可以一个负数,表示从后往前数的字符个数;
plain 表示是否使用简单模式,默认为 false,true 只做简单的查找子串的操作,false 表示使用使用正则模式匹配。
string.reverse(arg)字符串反转
string.format(...)返回一个类似printf的格式化字符串
string.char(arg) 和 string.byte(arg[,int])char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)
string.len(arg)计算字符串长度
string.rep(string, n)返回字符串stringn个拷贝
string.gmatch(str, pattern)返回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil
string.match(str, pattern, init)string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记,则返回整个配对字符串. 当没有成功的配对时, 返回nil
sub()字符串截取,原型为string.sub(s, i [, j])
s:要截取的字符串。
i:截取开始位置。
j:截取结束位置,默认为 -1,最后一个字符

具体用法请参考:Lua 字符串 | 菜鸟教程 (runoob.com)

常见的转移字符

image-20230204190948552

11、数组

这里的数组起始是属于一种特殊的table,它是table的子集。table的key可以是数值类型或者是字符串类型,但是数组的key就只能是数值类型,同样的数组的key也是可以改动。

  • 数组的定义:就是相同数据类型的元素按一定顺序排列的集合

  • 数组的分类

    • 一维数组:一维数组是最简单的数组,其逻辑结构是线性表

      array = {"Lua", "Tutorial"}for i= 0, 2 doprint(array[i])  --> nil Lua Tutorial
      end
      
    • 多维数组:多维数组即数组中包含数组或一维数组的索引键对应一个数组

      -- 初始化数组
      array = {}
      for i=1,3 doarray[i] = {}for j=1,3 doarray[i][j] = i*jend
      end-- 遍历数组
      for i=1,3 dofor j=1,3 doprint(array[i][j]) --> 1 2 3 4 5  6 7 8 9end
      end
      

12、迭代器

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

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

Lua中迭代器分为三种:

  • 泛型for迭代器

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

    for k, v in pairs(t) doprint(k, v)
    end
    

    示例

    array = {"Google", "Runoob"}for key,value in ipairs(array)
    doprint(key, value)
    end
    -- 输出
    1  Google
    2  Runoob
    
  • 无状态的迭代器

    无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价

    一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素,元素的索引需要是数值。

    以下实例我们使用了一个简单的函数来实现迭代器,实现 数字 n 的平方

    function square(iteratorMaxCount,currentNumber)if currentNumber<iteratorMaxCountthencurrentNumber = currentNumber+1return currentNumber, currentNumber*currentNumberend
    endfor i,n in square,3,0
    doprint(i,n)
    end-- 输出
    1    1
    2    4
    3    9
    
  • 多状态迭代器

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

    array = {"Google", "Runoob"}function elementIterator (collection)local index = 0local count = #collection-- 闭包函数return function ()index = index + 1if index <= countthen--  返回迭代器的当前元素return collection[index]endend
    endfor element in elementIterator(array)
    doprint(element)
    end-- 输出
    Google
    Runoob
    

13、表

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

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

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

Lua也是通过table来解决模块(module)、(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string

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

-- 初始化表
mytable = {}-- 指定值
mytable[1]= "Lua"-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存

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

示例:

mytable = {}
print("mytable 的类型是 ",type(mytable)) --> tablemytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])  --> Lua
print("mytable 索引为 wow 的元素是 ", mytable["wow"])  --> 修改前-- alternatetable和mytable的是指同一个 table
alternatetable = mytable
print("alternatetable 索引为 1 的元素是 ", alternatetable[1])  --> Lua
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])  --> 修改前-- 修改alternatetable
alternatetable["wow"] = "修改后"
print("mytable 索引为 wow 的元素是 ", mytable["wow"])  --> 修改后-- 释放变量alternatetable
alternatetable = nil
print("alternatetable 是 ", alternatetable) -->  nil-- mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])  --> 修改后-- 释放mytable
mytable = nil
print("mytable 是 ", mytable)
API作用
table.concat (table [, sep [, start [, end]]])concatconcatenate(连锁, 连接)的缩写。 table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开
table.insert (table, [pos,] value)在table的数组部分指定位置(pos)插入值为value的一个元素。 pos参数可选, 默认为数组部分末尾
table.remove (table [, pos])返回table数组部分位于pos位置的元素. 其后的元素会被前移。pos参数可选, 默认为table长度, 即从最后一个元素删起
table.sort (table [, comp])对给定的table进行升序排序

详细用法请参考:Lua table(表) | 菜鸟教程 (runoob.com)

14、模块与包

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

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

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}-- 定义一个常量
module.constant = "这是一个常量"-- 定义一个函数
function module.func1()io.write("这是一个公有函数!\n")
end-- 定义一个局部函数,对于外部而言,这是模块的私有化方式
local function func2()print("这是一个私有函数!")
endfunction module.func3()func2()
endreturn module
  • require 函数

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

    require("<模块名>")
    

    示例:

    -- test_module.lua 文件
    -- module 模块为上文提到到 module.lua
    require("module")print(module.constant)module.func3()
    
  • 模块记载机制

    对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化,当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里。

    #LUA_PATH
    export LUA_PATH="~/lua/?.lua;;"
    
  • Lua调用C的库

    同故宫

    local path = "/usr/local/lua/lib/libluasocket.so"
    -- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
    local f = assert(loadlib(path, "luaopen_socket"))  -- 不初始化该库
    f()  -- 真正打开库(此时初始化库)
    

    备注loadlib函数将path对应的C库封装成一个对象,然后返回给一个变量(相当于构造器new),但是它并不会初始化该库(也就是不会执行C库中的初始化函数,也就是在创建对象时,不会去执行构造方法),我们需要通过接收该库的变量f去初始化该该库。loadlib函数加载库出错会报错或者返回nilassert可以先检测错误然后调用初始化函数。

15、元表

前面我们学习了表(table),对于table中的元素我们可以进行许多操作(比如拼接、加、减),但是对于表和表之间却无法操作。

这时候就需要使用元表来设置两表之间的操作。

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

    mytable = {}                          -- 普通表
    mymetatable = {}                      -- 元表
    setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表
    -- 简写
    mytable = setmetatable({},{})getmetatable(mytable)                 -- 这会返回 mymetatable
    
  • getmetatable(table):返回对象的元表(metatable)

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

    示例

    other = { foo = 3 }
    t = setmetatable({}, { __index = other })
    t.foo  -->3
    t.bar  --> nil
    

    备注:t表为{},它的元表为{ __index = other },t.foo会先到t表中寻找foo键,由于t为{}没有foo键,所以就去t的元表找__index,此时__index包含表other,而other中恰好有foo,所以foo就是3

    Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

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

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

    3. 判断元表有没有 __index 方法,如果 index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

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

    示例

    mymetatable = {}
    mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })print(mytable.key1)  --> value1mytable.newkey = "新值2"
    print(mytable.newkey,mymetatable.newkey)  --> nil    新值2mytable.key1 = "新值1"
    print(mytable.key1,mymetatable.key1)  --> 新值1    nil
    

    备注:mytable表为{key1 = “value1”},它的元表是{ __newindex = mymetatable },我们对mytable表中一个不存在的变量newkey赋值,此时会去查找元表中是否含有__newindex,结果含有这个就执行__newindex元方法(即将这个赋值的值添加到__newindex对应的表中)而不进行赋值,而遇到mytable.key1,此时mytable表中有key1,就直接进行了赋值

16、协同程序

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西(协同是非常强大的功能,但是用起来也很复杂)

  • 协同程序和线程的区别:线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。

    协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同

    image-20230205143505774

参考资料

  • Lua官方文档
  • Lua 教程 | 菜鸟教程 (runoob.com)
  • Lua 5.3 参考手册
  • Lua 教程_w3cschool
  • Lua教程-Lua开发-Lua入门教程-嗨客网 (haicoder.net)

  1. 二元运算符:是指符号的运算需要两个数据参与 ↩︎

  2. 左连接:是指多个相同的运算符共同参与,都是以最左边优先,和有连接是相反的 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/15131.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于stm32单片机的智能门禁系统设计

一.硬件方案 基于stm32单片机的智能门禁系统设计的整体电路主要由采用STM32F103单片机&#xff0c;4*4矩阵按键电路&#xff0c;电子锁继电器驱动电路&#xff0c;开锁LED指示灯&#xff0c;LCD1602显示屏电路&#xff0c;蜂鸣器报警电路&#xff0c;RFID感应电路&#xff0c;…

附录7-用户列表案例,element-ui

目录 1 效果 1.1 查询所有用户 1.2 添加新用户 1.3 删除用户 1.4 用户详情 2 后端 2.1 查询所有 2.2 添加 2.3 删除 2.4 查询单个 3 前端 3.1 环境 3.2 main.js 3.3 userList.vue 3.4 userInfo.vue 1 效果 1.1 查询所有用户 1.2 添加新用户 …

Chrome将更换HTTPS的“小绿锁”图标

HTTPS 是一种用于安全地传输数据的网络协议。HTTPS 是在 HTTP 协议的基础上添加了 SSL/TLS 协议&#xff0c;通过对数据进行加密&#xff0c;从而保证数据传输的安全性。HTTPS 常用于保护敏感信息&#xff0c;如个人身份信息、银行账户信息、信用卡信息等。 目前市面上所有的浏…

适合Docker的场景以及不适合的场景

文章来自&#xff1a;When to use and when not to use Docker和7 Cases When You Should Not Use Docker&#xff0c;以及互联网网上的一些零散内容。这篇文章只是基于我自己的理解&#xff0c;进行简单的概述。 适合的场景 你的团队不是一成不变的。 当有新的成员加入&#…

Openlayers实战:回显多点、多线段、多多边形

Openlayers地图中,回显数据是非常重要的。 继上一示例回显点、线、圆形、多边形后。本示例回显多线,多点,多个多边形。用到了MultiPoint,MultiLineString,MultiPolygon。 多个信息的显示可以采用循环的方式,单个显示点、线、面。 但是循环方式是要多次计算的,而MultiPoint…

FreeRTOS ~(六)信号量 ~ (1/3)信号量解决同步缺陷

前情提要 FreeRTOS ~&#xff08;四&#xff09;同步互斥与通信 ~ &#xff08;1/3&#xff09;同步的缺陷 FreeRTOS ~&#xff08;五&#xff09;队列的常规使用 ~ &#xff08;1/5&#xff09;队列解决同步缺陷 举例子说明&#xff1a;利用信号量解决前述的"同步的缺陷&…

运动控制介绍

运动控制介绍 1 介绍1.1 概述1.2 运动控制的基本架构1.3 常见的控制功能1.4 运动控制研究的问题分类位置变化问题周期式旋转速度变化问题 1.5 知识体系1.6 路径规划 和 轨迹规划区别与联系1.7 运动控制系统 2 《运动控制系统》[班华 李长友 主编] 摘要1 绪论1.1 运动控制研究的…

【数据仓库】FineBI数据可视化使用体验

FineBI介绍 FineBI是新一代自助式BI工具,企业客户多,服务范围广.凭借finebi简单流畅的操作,强劲的大数据性能和自助式的分析体验。 1&#xff0c;对个人用户来说&#xff0c;免费的无限期试用&#xff0c;解锁所有功能&#xff0c;除了限制两个并发访问&#xff0c;个人用户可以…

CSS 两行文字两端对齐与字符间距的处理

前言 &#x1f44f;CSS 文字对齐与字符间距的处理&#xff0c;在这里&#xff0c;你可以了解到文字渐变&#xff0c;letter-spacing&#xff0c;text-align&#xff0c;text-align-last&#xff0c;filter等&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关…

42. 会话划分问题

文章目录 题目需求思路一实现一题目来源 题目需求 现有页面浏览记录表&#xff08;page_view_events&#xff09;如下&#xff0c;每行数据代表&#xff1a;每个用户的每次页面访问记录。 规定若同一用户的相邻两次访问记录时间间隔小于60s&#xff0c;则认为两次浏览记录属于…

CSS 内容盒子与边框盒子

这篇比较重要&#xff0c;会不断更新❗ 文章目录 &#x1f447;内容盒子开发者工具的使用border 边框padding 内边距margin 外边距盒子整体尺寸元素默认样式与CSS重置元素分类块级标记行级标记行内块标记 display样式内容溢出裁剪掉溢出部分滚动条 圆角边框 border-radius ✌边…

学无止境·MySQL③

单表查询 题一创建表并插入数据薪水修改为5000将姓名为张三的员工薪水修改为3000元将姓名为李四的员工薪水修改为4000元&#xff0c;gener改为女 题一 1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff…