Lua 15分钟快速上手(上)

2023-11-10

本系列相关文章:
Flutter 热更新及动态UI生成
Lua 15分钟快速上手(上)
Lua 15分钟快速上手(下)
Lua与C语言的互相调用
LuaDardo中Dart与Lua的相互调用

在之前的博客《Flutter 热更新及动态UI生成》一文中,通过编写LuaDardo虚拟机,大致介绍了在Dart语言之上开发Lua虚拟机给Flutter提供动态能力的方案,但Lua语言流行并不算广泛,许多人对小巧精湛的Lua语言缺少了解,认为将Lua替换为JavaScript语言更好。为此,我特别整理了两篇Lua语言的快速上手指南,相信充分学习了解后,也会感觉到在特定需求场景下,小巧简洁的Lua将更具“胶水”优势。

基础语法篇

注释

  • 单行注释
  • 多行注释
-- 单行注释,使用两个减号


--[[
多行注释
多行注释
多行注释
]]

数据类型

Lua 中有 8 种基本类型

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({}))                 --> table
print(type(io.stdin))           --> userdata

变量

Lua 变量有三种类型:

  • 全局变量

    默认情况下,Lua中所有的变量都是全局变量

  • 局部变量

    使用local 显式声明在函数内的变量,以及函数的参数,都是局部变量。在函数外即使用local去声明,它的作用域也是当前的整个文件,这相当于一个全局变量。

  • 表中的域

注意,变量的默认值均为 nil。Lua语言不区分未初始化变量和被赋值为nil的变量,因此全局变量无须声明即可使用。在Lua中,应尽可能使用局部变量,这有两个好处:

  1. 避免命名冲突
  2. 访问局部变量的速度比全局变量更快
a = 5               -- 全局变量
local b = 5         -- 局部变量

function joke()
    c = 5           -- 全局变量
    local d = 6     -- 局部变量
end

a, b, c = 0, 2, 7   -- Lua支持多变量赋值

流程控制

代码块

在其他语言中,代码块会使用一对花括号"{“和”}"括起来的,在Lua中是使用关键字括起来

do
print("Hello")  -- 使用do end包裹一个代码块
end

逻辑分支语句

if语句格式

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

代码示例

if( a < 20 ) then
   -- if条件为 true 时执行
   print("a 小于 20" );
end

--[ if...else语句 --]
if( a < 20 ) then
   print("a 小于 20" )
else
   print("a 大于 20" )
end

if...elseif...else多重判断

a = 100

if( a == 10 ) then
   print("a 的值为 10" )
elseif( a == 20 ) then   
   print("a 的值为 20" )
elseif( a == 30 ) then
   print("a 的值为 30" )
else
   print("没有匹配 a 的值" )
end

注意,Lua中的if语句也可以进行嵌套,但是建议优先考虑使用if...elseif...else来组合这些条件判断。Lua中没有switch语句。

循环语句

主要有三种循环

while循环

-- while循环

a=10
while( a < 20 ) do
   print("a 的值为:", a)
   a = a+1
end

for循环

for循环有两种

  • 数值for循环

    如下,var从exp1变化到exp2,每次变化以exp3为步长递增var。exp3是可选的,如果不指定,默认为1

    for var=exp1,exp2,exp3 do  
        <执行体>  
    end  
    
  • 泛型for循环

    通过一个迭代器函数来遍历所有值,类似于Java的foreach循环

    for i,v in ipairs(a) do 
    	<执行体>
    end  
    

代码示例

-- 数值型for循环,等价于C语言:for(int i=0,i<=9,i++)
for i = 0,9,1 do
    print(i)
end

-- 遍历一个数组
days = {"Suanday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}  
for i,v in ipairs(days) do  
    print(i, v) 
end

需要注意,除了ipairs函数外,还有另一迭代器函数pairs也可用于遍历。

这两个函数的区别在于:ipairs 仅仅遍历值,按照索引升序遍历,索引中断停止遍历。即不能返回 nil,只能返回数字 0,如果遇到 nil 则退出。它只能遍历到集合中出现的第一个不是整数的 key。pairs 能遍历集合的所有元素。即 pairs 可以遍历集合中所有的 key,并且除了迭代器本身以及遍历表本身还可以返回 nil。

local tab = {
    [2] = "test2",
    [6] = "test3",
    [4] = "test1"
}

for k, v in pairs(tab) do  -- 使用ipairs则无法正确遍历,因为索引不是正确有序的
    print(k, v)
end

repeat循环

相当于其他语言的do…while循环,它是条件后行,即循环的条件语句在当前循环结束后去判断,因此循环体中语句至少要执行一次

repeat
   statements
while( condition )

代码示例

a = 10

repeat
   print("a的值为:", a)
   a = a + 1
until( a > 15 )

循环控制

Lua中支持循环控制语句breakreturngoto,但要明确三者的区别

  • breakreturn语句都可用于跳出循环,但break用于跳出最内层循环,而return用于返回函数的执行结果或简单地结束函数的运行
  • 类似于C语言的goto语句,用于将当前程序跳转到相应的标签处继续执行

Lua中没有continue语句,这里我们使用goto模仿一个continue语句

while true do
    if true then
        -- 跳转到标签continue处
        goto continue
    end
    
    -- some code

    ::continue::
end

标签名可以是任意有效的标识符。标签的语法格式:标签名称前后紧跟两个冒号,例如::continue::

注意,在使用goto跳转时,Lua语言设置了一些限制条件。

  • 首先,标签遵循常见的可见性规则,因此不能直接跳转到一个代码块中的标签(因为代码块中的标签对外不可见)
  • 其次,goto不能跳转到函数外(注意第一条规则已经排除了跳转进一个函数的可能性)
  • 最后,goto不能跳转到局部变量的作用域

函数

一个Lua程序既可以调用Lua语言编写的函数,也可以调用C语言(或者宿主程序使用的其他任意语言)编写的函数。一般来说,我们选择使用C语言编写的函数来实现对性能要求更高,或不容易直接通过Lua语言进行操作的操作系统机制等。例如,Lua语言标准库中所有的函数就都是使用C语言编写的。不过,无论一个函数是用Lua语言编写的还是用C语言编写的,在调用它们时都没有任何区别。

声明一个函数的格式:

optional function 函数名(形参列表)
    -- 函数体
end

其中optional是可选的,用于指明函数是全局函数还是局部函数,默认为全局函数;使用关键字 local则为局部函数。

-- 声明一个求最大值的函数
function max(num1, num2)
   if (num1 > num2) then
      result = num1;
   else
      result = num2;
   end

   return result; 
end

print(max(10,4))

Lua函数支持多值返回

function getAddr()
    return "127.0.0.1",8080
end

ip, port = getAddr()
print(ip,port)

Lua函数还支持可变参,但有其他固定参数时,可变参必须放置到参数列表的最后

-- 参数列表中使用三个点表示可变参
function average(...)
    local sum = 0
    local arg={...}
    for i,v in ipairs(arg) do
        sum = sum + v
    end
    return sum/#arg  -- #用于获取表长度
end
 
 print("平均值为",average(10,5,3,4))

特别注意

  • 调用函数时使用的参数个数可以与定义函数时使用的参数个数不一致。Lua语言会通过抛弃多余参数和将不足的参数设为nil的方式来调整参数的个数。

  • 函数调用时需要一对圆括号,但当函数只有一个参数且该参数是字符串常量或表构造器时,括号是可省略的

    print "hello,world"  -- print("hello,world")
    func{a=1,b=2}        -- func({a=1,b=2})
    type{}               -- type({})
    

匿名函数与闭包

-- 匿名函数
add = function (a,b)
    return a+b
end

print(add(10,8))

我们可以在函数中定义函数

-- 在函数中创建一个新的函数sub
function create()
    function sub(x,y)
        return y-x
    end
end

create()
-- 由于Lua中默认变量是全局变量,因此sub是全局变量,外部可使用
print(sub(10,8))

-- 添加local即变为局部变量
function create()
    local function sub(x,y)
        return y-x
    end
end

结合上述匿名函数示例,我们可以将代码写得更像函数式编程

function create()
    local sub = function(x,y)
        return y-x
    end

    -- do something
end

使用示例

-- 声明一个函数,接受函数类型变量做形参
function calc(a,b,callback)
    print(callback(a,b))
end

-- 调用calc,传入一个做加法的匿名函数
calc(10,8,function (a,b)
    return a+b
end)

-- 传入一个做减法的匿名函数
calc(10,8,function (a,b)
    return a-b
end)

运算符

Lua提供了以下几种运算符类型:

  • 算术运算符

    +-*/%^

    同C语言,但多出了一种负号运算符,例如:-7

  • 关系运算符

    同C语言,但请注意,Lua的不等于使用~=,而不是!=

  • 逻辑运算符

    andornot表示与、或和非

  • 其他运算符

    运算符 描述 实例
    .. 连接两个字符串 str="hello".." world"
    # 返回字符串或表的长度 len = #"hello" 值为 5

运算符优先级

优先级从高到低

2020-10-23-001

字符串

Lua 语言中字符串三种表示方式:

  • 单引号括起的一串字符
  • 双引号括起的一串字符
  • [[]]括起的一串字符,可以包含多行
str1 = "this is a string"
str2 = 'this is a string'
str3 = [[
    this is a string
    this is a string
    this is a string
    ]]

注意:字符串也可以用长括号括起来。长括号由两个方括号[,]组成,其间有零个或多个等号=。当等号的数量为N时,它被称为 “N级长括号”。以下是长括号的例子:

  • [[ 开始0级的长括号
  • ]] 闭合第0级的长括号
  • [=[ 开始第1级的长括号
  • ]=] 闭合第1级的长括号
  • [===[ 开始第3层的长括号
  • ]===] 闭合第3层的长括号

在长符串中,从同一水平的开括号到闭括号的部分是字符串。 与使用双引号或单引号的形式的字符串不同,长字符串可以包含换行符而无需特殊的转义符号。 请注意,当一个长字符串开始的括号后紧接一个换行符时, 这个换行符不会放在字符串内。

以下示例将相同的字符串分配给变量a:

a = 'abc\n123'
a = "abc\n123"
a = [[abc
123]]
a = [==[
abc
123]==]

字符串操作函数

  • string.upper(arg)string.lower(arg) 字符串全部转大写、小写

  • string.char(arg)string.byte(arg[,int]) char 将整型编码转成字符并连接, byte 转换字符为整型编码

    print(string.char(97,98,99,100))
    
  • string.len(arg) 计算字符串长度(计算ASCII字符串,不能用于计算中文)

  • string.format(...) 类似于C语言的printf,使用占位符格式化字符串

    print(string.format("the value is:%d",4))
    
  • string.reverse(arg) 字符串反转

  • string.gsub(mainString,findString,replaceString,num)

    在字符串中替换。mainString为要替换的字符串, findString 为被替换的字符,replaceString 要替换的新字符,num 替换次数(忽略,则全部替换)

    string.gsub("aaaa","a","z",3)  --> zzza 3
    
  • string.find(str, substr, [init, [end]])

    在一个指定的目标字符串中搜索指定的内容(第三个参数为索引,可忽略),返回其具体位置。不存在则返回 nil

    print(string.find("Hello Lua", "Lua", 1))
    
  • string.sub(string, i,[j]) 从一个字符串中截取子串,返回的是一个新字符串,而不是修改原字符串。i、j为范围索引,左闭右开

--[[
字符转换
]] 

-- 转换第一个字符
print(string.byte("Lua"))
-- 转换第三个字符
print(string.byte("Lua",3))

-- 转换末尾第一个字符
print(string.byte("Lua",-1))
-- 转换末尾第二个字符
print(string.byte("Lua",-2))

-- ASCII 码转换为字符
print(string.char(97))

--[[
字符串转换
]] 
print(tonumber("18"))   -- 字符串转数值
print(tostring(20))     -- 数值转字符串

--[[
字符串拼接
]] 

str1 = "www."
str2 = "bczl"
str3 = ".xyz"

-- 使用 .. 进行字符串连接
print("拼接字符串",str1..str2..str3)

-- 字符串于整数拼接,返回的仍是字符串
num = "12"..1
print(num)

特别注意:

string.len()以及#用于计算ASCII字符串长度,当传入Unicode字符串时,计算得到的是字节数而不是字符数。当我们需要处理中文等Unicode字符串时,应使用utf8标准库中的函数。

-- lua5.3新增utf8标准库
print(utf8.len("这是中文"))

-- 遍历每个字符的编码
for i, c in utf8.codes("编程之路从0到1") do
    -- char函数将编码转为字符
    print(i,utf8.char(c))
end

表是Lua中唯一的数据结构。表本质上是一种辅助数组(associative array),这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型的值作为索引(nil除外),但要注意,表是不固定大小的,会自动扩容。

使用构造器表达式(constructor expression)创建表

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"
mytable["wow"] = "before"
-- 获取值
print("索引为 1 的元素是 ", mytable[1])
print("索引为 wow 的元素是 ", mytable["wow"])

同一个表中存储的值可以具有不同的类型索引。当把表当作结构体使用时,还可以把索引当作成员名称使用,例如:a.name等价于a["name"]

a = {}

a["name"] = "zhangsan"

-- 等价于a["name"]
print(a.name)
a.age = 19
print(a["age"])

对Lua语言而言,这两种形式是等价且可以自由混用的;不过,对于阅读代码的人而言,这两种形式可能代表了不同的意图。a.name的点分形式表达出此表是被当作结构体使用的,a["name"]的字符串索引形式则表达出此表被当作类似字典的数据结构使用。

注意:a.xa[x]常常被混淆。实际上,a.x代表的是a[“x”],即由字符串"x"索引的表;而a[x]则是指由变量x对应的值索引的表,若不注意此问题,会导致诡异的Bug!

a = {}

x = "y"
a[x] = 10

print(a[x])  -- 等价 a["y"]
print(a.x)   -- 等价 a["x"] 打印 nil
print(a.y)   -- 等价 a["y"]

此外,a[2.0]a[2]是等价的,浮点数作表索引时,任何能够被转换为整型的浮点数都会被转换成整型数,不能被转换的,如a[2.1]则不会发生转换。

表构造器有三种字面量初始化方式

-- 1. 列表式构造。创建的是数组(一种特殊的表,索引是整数,类似其他语言的列表)
fruits = {"banana","orange","apple"}
-- 2. 记录式构造。索引可以是其他值,类似字典
a = {x=1,y=2,z=3}
-- 3. 通用构造器。
op = {["+"]="add",["-"]="sub"}

总结:

  1. Lua 没有其他语言的列表这种数据结构,索引为正整数的表,即用来表示列表功能。

    {"a","b","c"}  --> 等价于 {[1]="a",[2]="b",[3]="c"} 
    
  2. **Lua 中序列的索引不是从0而是从1开始,这和我们的编程常识不同,需要特别注意!**数组的第一个元素是a[1]

  3. 列表式构造和记录式构造可以混合使用

    -- 混合两种表构造器
    a = {x=1,y=2,"lua",n=100,"dart","java"}
    print(a[1])  -- lua
    print(a[2])  -- dart
    print(a[3])  -- java
    
  4. 前两种构造器都不能使用负数做索引初始化表元素,也不能使用不符合规范的标识符作为索引!对于这类需求,需要使用第三种通用构造器。

    -- +86做索引错误!
    -- {+86="phone"}
    {["+86"]="phone"} -- 正确
    

用表模拟数组(列表)

array = {}
-- 获取数组长度
print(#array)  -- 0

-- 初始化数组
for i = 1, 10 do
    array[i] = i*2
end
-- 再次获取数组长度
print(#array) -- 10

由于未初始化的元素均为nil,所以可以利用nil值来标记数组或列表的结束。Lua中把所有元素都不为nil的数组称为序列,并提供了获取序列长度的操作符#。长度操作符也为操作序列提供了几种写法:

print(a[#a])  -- 打印序列a的最后一个元素
a[#a] = nil   -- 删除序列的最后一个元素
a[#a + 1] = v -- 把v的值加入到序列的末尾

对于中间存在空洞(nil值)的列表而言,序列长度操作符是不可靠的,它只能用于序列。更准确地说,序列是由指定的n个正数数值类型的键所组成集合{1,...,n}形成的表(请注意值为nil的键实际不在表中)。特别地,不包含数值类型键的表就是长度为零的序列。

对于Lua语言而言,一个为nil的字段和一个不存在的元素没有区别:

a = {10,20,nil,nil} -- 等价于 {10,20}
print(#a)

但是很多列表是通过逐个添加元素创建出来的。任何按照这种方式构造出来的带有空洞的列表,其最后一定存在为nil的值,只是nil可能存在几个元素中间,因此存在空洞的列表的行为是Lua语言中最具争议的内容之一。

表操作

  • table.concat(table, separator, start, end)

    接收一个字符串数组并返回字符串连接后的结果。元素间以指定分隔符separator隔开。除了table外,其他参数都是可选的。separator默认是空字符,start默认为1,end默认为数组的长度

  • table.insert(table, position, value)

    将元素value插入到数组的指定位置。position是可选的,默认为数组末尾

  • table.remove(table, position)

    删除并返回数组指定位置上的元素。删除后所有元素前移,以填补空隙。若不指定位置,则会删除数组的最后一个元素

  • table.sort(table, compare)

    对给定的数组进行升序排序,其中compare为可选参数,可以是一个外部函数用来自定义排序标准。排序函数的标准是接收两个参数并返回一个布尔型的值,若返回值为true则表示升序,反之则为降序

  • table.move (a1, f, e, t [,a2])

    将表a中从索引f~e的元素(包含f和e位置的元素)移动到位置t上。注意,目标范围可以与源范围重叠。计算机领域移动(move)通常指将一个值从一个地方拷贝(copy)到另一个地方。当带有可选参数a2时,该函数将第一个表中的元素复制到第二个表中,相当于克隆操作。

local arr = {"alpha", "beta"}

table.insert(arr,1, "delta")
table.insert(arr,2, "epsilon")
table.insert(arr, "zeta")

print(table.concat(arr, ","))

table.remove(arr,2)
table.remove(arr)

-- 对数组排序
table.sort(arr)
print(table.concat(arr, ","))


local a1 = {"a","b","c","d"}
local a2 = {}
table.move(a1 ,1,#a1,4)
print(table.concat(a1, ","))  --> a,b,c,a,b,c,d
table.move(a1 ,1,#a1,1,a2)
print(table.concat(a2, ","))  --> a,b,c,a,b,c,d

Lua 15分钟快速上手(下)


关注公众号:编程之路从0到1
编程之路从0到1

或关注博主的视频网校

云课堂

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Lua 15分钟快速上手(上) 的相关文章

随机推荐

  • java pointer_Java EE 8 JSON Pointer讲解

    Java EE 8包含JSON处理API的更新 并为最新的JSON标准提供最新的IEFT标准 他们是 JSON Pointer RFC 6901 JSON Patch RFC 6902 JSON Merge Patch RFC 7396 入
  • TCP中 滑动窗口RWND 和 拥塞窗口 CWND的区别

    滑动窗口RWND 和 拥塞窗口 CWND的区别 参考文章 What is CWND and RWND 文章如有错误 希望指正 共同学习 RWND Receiver Window 滑动窗口 滑动窗口技术是TCP的流量控制的核心 存在于TCP的
  • shell的字符串和数字的转化(数字自动做字符串处理,变量名做字符串输出用单引号)

    shell里面怎么样把字符串转换为数字 例如 a 024 1 用 a 2 用let达到 运算效果 let num 0123 echo num 83 3 双括号运算符 a 1 2 echo a 等同于 a expr 1 2 而数字会默认做字符
  • mfc 服务器文件拷贝到本地,mfc服务器客户端间传输文件

    mfc服务器客户端间传输文件 内容精选 换一换 要访问您的文件系统 如果是Linux云服务器 您需要在Linux云服务器上安装NFS客户端后使用挂载命令挂载文件系统 如果是Windows云服务器 您需要在Windows云服务器上安装NFS客
  • 设计模式二三事

    设计模式是众多软件开发人员经过长时间的试错和应用总结出来的 解决特定问题的一系列方案 现行的部分教材在介绍设计模式时 有些会因为案例脱离实际应用场景而令人费解 有些又会因为场景简单而显得有些小题大做 本文会结合在美团金融服务平台设计开发时的
  • 为什么连接HBase报错:Will not attempt to authenticate using SASL (unknown error)?

    问题现象 访问CloudTable的HBase连接不上 出现如下所示的错误信息 Opening socket connection to server 192 168 0 107 192 168 0 107 2181 Will not at
  • Java-主流框架—(1)Spring--IoC

    1 Spring介绍 1 1什么是框架 软件工程框架 经过验证的 具有一定功能的 半成品软件 经过验证 具有一定功能 半成品 1 2框架的作用 1 3Spring是什么 Spring是分层的JavaSE EE应用full stack轻量级开
  • 泛型应用

    region 私有方法
  • flask 框架

    Flask本身只是建立了一个Web框架 很多功能都需要在Flask基础上进行扩展 如果是高手 可以手打一个Flask Extension 普通人可以用已经开源扩展进行二次开发 用过的几个Flask的扩展库 目前为止Flask Securit
  • 【C++】Windows系统&Linux系统获取uuid统一通用代码

    ConsoleApplication1 cpp 定义控制台应用程序的入口点 include stdafx h include
  • Qt进程间通信

    简述 进程间通信 就是在不同进程之间传播或交换信息 通信方式 管道 pipe 管道是一种半双工的通信方式 数据只能单向流动 而且只能在具有亲缘关系的进程间使用 进程的亲缘关系通常是指父子进程关系 有名管道 named pipe 有名管道也是
  • java中访问修饰符的权限

    在java中 最常见的访问修饰符有以下四个 public default 默认 即不写修饰符 protected private 它们主要用来规定所修饰成员的开放程度 用来保护成员不被随意调用和修改 在新建一个类时 只能用public和de
  • 【区块链】深度长文:2018新风口,区块链3.0时代即将来临?

    徐小平说 区块链将掀起一场革命 1月9日 徐小平在真格投资组合群里分享了一段关于拥抱区块链时代的内容 并表示不能外传 被泄露的微信截图 岂料 很快去传了出去 他表示 这本是其与被投公司间的 低调 内容分享 现在被人擅自传出去 也没办法 最后
  • Express基本认识

    express是一个基于nodejs 且快速 开放的一个web开发框架 安装命令 yarn add express 查看express的所有版本 npm view express versions 搭建基本的express程序 const
  • ./configure --prefix=

    一直用这个选项prefix 但不知道 啥意思 转载自 inux安装软件采用源码安装灵活自由 适用于不同的平台 维护也十分方便 源码的安装一般由3个步骤组成 配置 configure 编译 make 安装 make install 具体的安装
  • centos7设置开机为命令行启动

    图形界面默认安装之后 每次启动都是图形界面启动 图形界面需要占用系统大量的内存和CPU资源 对于个人电脑和服务器 将Centos 默认启动改为文本方式 会显著提高运行效率 方法一 不修改默认启动方式 root模式下 init 3 gt 切换
  • sqlserver查看执行计划

    方式一 通过Microsoft sql server management studio工具栏中的 显示估计的执行计划 按钮 选中SQL 然后点击该按钮 SQL就会给我们选中SQL的图形执行计划 方式二 set showplan all o
  • 【无监督学习】0、有监督学习、无监督学习、半监督学习

    文章目录 一 有监督学习 二 半监督学习 三 无监督学习 3 1 对比式学习 一 有监督学习 有监督学习最大的特点就是数据集是带标签的 如有监督分类任务 就是给每张图都分配一个真实标签 表示这张图是 dog cat 或者是 bird 而标签
  • 【iOS】内存管理

    文章目录 前言 理解引用计数 引用计数原理 属性存取方法中的内存管理 自动释放池 保留环 以ARC简化引用计数 使用ARC时必须遵守的命名规则 变量的内存管理语义 ARC如何清理实例变量 覆写内存管理的方法 在dealloc方法中只释放应用
  • Lua 15分钟快速上手(上)

    本系列相关文章 Flutter 热更新及动态UI生成 Lua 15分钟快速上手 上 Lua 15分钟快速上手 下 Lua与C语言的互相调用 LuaDardo中Dart与Lua的相互调用 在之前的博客 Flutter 热更新及动态UI生成 一