热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Lua15分钟快速上手(上)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Lua15分钟快速上手(上)相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Lua 15分钟快速上手(上)相关的知识,希望对你有一定的参考价值。





在之前的博客《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 = 027   -- 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,但要明确三者的区别





  • break
    return语句都可用于跳出循环,但
    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(argdo
        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




「运算符优先级」


优先级从高到低



Lua 15分钟快速上手(上)

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 = 110 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





或关注博主的视频网校




云课堂



推荐阅读
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 本文整理了一份基础的嵌入式Linux工程师笔试题,涵盖填空题、编程题和简答题,旨在帮助考生更好地准备考试。 ... [详细]
  • C语言编写线程池的简单实现方法
    2019独角兽企业重金招聘Python工程师标准好文章,一起分享——有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带 ... [详细]
  • Leetcode学习成长记:天池leetcode基础训练营Task01数组
    前言这是本人第一次参加由Datawhale举办的组队学习活动,这个活动每月一次,之前也一直关注,但未亲身参与过,这次看到活动 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 本文详细介绍了批处理技术的基本概念及其在实际应用中的重要性。首先,对简单的批处理内部命令进行了概述,重点讲解了Echo命令的功能,包括如何打开或关闭回显功能以及显示消息。如果没有指定任何参数,Echo命令会显示当前的回显设置。此外,文章还探讨了批处理技术在自动化任务执行、系统管理等领域的广泛应用,为读者提供了丰富的实践案例和技术指导。 ... [详细]
  • 短视频app源码,Android开发底部滑出菜单首先依赖三方库implementationandroidx.appcompat:appcompat:1.2.0im ... [详细]
  • 本文探讨了 TypeScript 中泛型的重要性和应用场景,通过多个实例详细解析了泛型如何提升代码的复用性和类型安全性。 ... [详细]
  • 自然语言处理(NLP)——LDA模型:对电商购物评论进行情感分析
    目录一、2020数学建模美赛C题简介需求评价内容提供数据二、解题思路三、LDA简介四、代码实现1.数据预处理1.1剔除无用信息1.1.1剔除掉不需要的列1.1.2找出无效评论并剔除 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 本文深入探讨了MDK链接脚本的应用与优化技巧。首先,文章介绍了链接脚本的基本概念及其在嵌入式系统开发中的重要性。接着,通过具体实例详细分析了链接脚本的结构和功能,特别是在程序在FLASH中运行时,如何优化链接脚本以提高系统性能。此外,文章还讨论了无需将程序加载到SRAM中的技术细节,为开发者提供了实用的参考和指导。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
author-avatar
静候轮回
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有