白话Lua教程

Important

本教程为零基础教程,适合广大没有接触过Lua的用户进行阅读。
对有编程经验的用户来说,可能过于啰嗦,可以点击左侧的简明Lua语法查看简易文档。
建议读者在阅读时多运行代码进行测试,以确保牢记和巩固知识点。
为了方便各位测试Lua代码,我们提供了Lua在线调试网站,点击即可访问


初始

初识Lua

Lua 是一种轻量小巧的脚本语言,它用标准C语言编写并以源代码形式开放。这意味着什么呢?这意味着Lua虚拟机可以很方便的嵌入别的程序里,从而为应用程序提供灵活的扩展和定制功能。而整个Lua虚拟机编译后仅仅一百余K,经过适当的裁剪还能做到更小,十分适合嵌入式的开发。

同时,在目前脚本引擎中,Lua的运行速度占有绝对优势。这些都决定了Lua是作为嵌入式脚本的最佳选择。

在我们编写代码之前,需要做一下准备,就是:确保你输入标点符号时,用的不是中文输入法。


第一个 Lua 程序

几乎所有语言的第一行代码,都是输出hello world,本教程也不意外。

Lua中,打印结果只需要使用print这个函数即可。同时,如果需要使用函数,只需要在函数名后加上双括号,同时传入你想传入的值即可。

所以,我们来执行下面的代码,打印出hello world吧!

print("hello world!")

输出数据

上一部分,我们知道了,在Lua中,可以使用print函数来打印你想要得到的结果。

并且还知道了,函数是指可以实现某些功能的子程序,可以使用函数名(参数)来执行。

让我们试着输出一些其他东西吧!使用多个print函数,输出自己想输出的数据。

print("测试")
print("aabcdefg")
print("xxxxx","第二个参数","第三个参数")

代码注释

代码注释就是在代码里,不会运行的部分。注释完全不会被运行。

这部分是为了在查看代码时,可以更好地立即现有代码含义用的。

我们可以用--开头,来写一段单行注释

也可以用--[[开头,]]结尾,写一段多行注释。

下面是注释的例子:

print("这段代码会运行")
--print("我被注释掉了,所以不会运行")
--[[
    我是多行注释
    不管我写多少行
    都不会影响代码运行
]]

变量

number变量

变量,可以看作是一个桶,在里面装你想要装的内容。这些内容可以是Lua包含的所有合法类型。

例如:我想要新建一个桶,名叫bucket,在里面放入233这个数字,就可以像下面一样:

bucket = 233

让我们试着自己新建几个变量吧!

  • 新建变量year,并将变量的值设置为1926
  • 新建变量month,并将变量的值设置为8
  • 新建变量day,并将变量的值设置为7
--新建三个变量,并赋值
year = 1926
month = 8
day = 7
--打印出三个变量的值
print(year,month,day)

了解nil

nil类型表示没有任何有效值,只要是没有声明的值,它就是nil

比如我打印一个没有声明的值,便会输出nil

ccc = 233
print(ccc)
print(aaa)

这里需要你思考一下,运行以下代码,将会输出什么结果?

a = 1
b = '2'
c = a
print(a,b,c,d)

赋值语句

赋值是改变一个变量值的最基本的方法。

如下面一样,使用等号左边变量进行赋值

n = 2
n = 3
n = n + 1
b = n

Lua可以对多个变量同时赋值,变量用逗号分开,赋值语句右边的值会依次赋给左边的变量

n = 1
a, b = 10, 2*n

当左右值的数量不一致时,Lua会进行下面的设定:

  • 变量个数 > 值的个数:按变量个数补足nil
  • 变量个数 < 值的个数:多余的值会被忽略

下面的例子可以展示这种设定:

a, b, c = 0, 1
print(a,b,c)
--输出0   1   nil

a, b = a+1, b+1, b+2
print(a,b)
--输出1   2

a, b, c = 0
print(a,b,c)
--输出0   nil   nil

这里需要你思考一下,运行以下代码,将会输出什么结果?

a,b,c = 1,2,3
a,c = a+1,b
d = c,b
print(a,b,c,d)

交换变量

这部分需要你自己完成一个任务:

已知下面的代码,并且已知ab的值,请在交换他们的值,使打印输出12 34

a = 34
b = 12

--你需要在这里进行交换变量的操作
--提示:新建一个变量来存放临时的值

print(a,b)

输出变量

我们已经知道了,在Lua中,可以使用print函数来打印你想要得到的结果。

同时在上一节,我们学会了新建变量和设置变量的值。

让我们试着输出某个变量吧!使用print函数,输出已知变量。 我们已知变量num为某个数字,试着输出它的值吧!

num = 123
--请补全代码,输出num的值
print(你要在这里填东西)

算数运算符

运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。

上一节中,新建的数字变量,我们称之为number类型的变量。

本节我们来学习使用算术运算符,如下所示:

+ 加法
- 减法
* 乘法
/ 除法
% 取余,求出除法的余数
^ 乘幂,计算次方
- 负号,取负值

我们可以通过以下实例来理解算术运算符的应用:

a = 21
b = 10
c = a + b
print('a + b 的值为 ', c )
c = a - b
print('a - b 的值为 ', c )
c = a * b
print('a * b 的值为 ', c )
c = a / b
print('a / b 的值为 ', c )
c = a % b
print('a % b 的值为 ', c )
c = a^2
print('a^2 的值为 ', c )
c = -a
print('-a 的值为 ', c )
c = a * (b - a)
print('a * (b - a) 的值为 ', c )

你需要完成下面的任务:

已知,一个长方体的长宽高分别为a、b、c(单位米),且这个物体重量为m(单位克)

请打印出物体的密度(单位g/m³)

注:密度计算公式 密度 = 质量 / 体积

a,b,c = 1,2,3 --长宽高
m = 10        --重量
--请打印出物体的密度

字符串

string类型变量

字符串(即string),就是一串文本数据,可以存储你要的文本。

在第二节中,print出的数据就是一个字符串。

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

  1. 单引号间的一串字符
  2. 双引号间的一串字符
  3. [[和]]间的一串字符

你可以参考下面的例子来深入理解:

--双引号间的一串字符
str1 = "Lua"
--单引号间的一串字符
str2 = 'Lua'
--[[和]]--间的一串字符
str3 = [[Lua]]
str4 = [[使用双括号时,甚至能包含换行数据
换行了
最后一行]]

--输出所有字符串
print(str1)
print(str2)
print(str3)
print(str4)

接下来你需要完成下面的练习:

新建三个变量s1s2s3

分别存入字符串数据:strabc233,使输出打印正确

--请在空白处补全代码

print("s1,s2,s3的值:",s1,s2,s3)
print("s1,s2,s3的类型:",type(s1),type(s2),type(s3))

转义字符

在上一节中,我们学习了如何声明字符串。

但是我们有时候会遇到一些特殊的问题,如:如何输出单引号和双引号?如何输出回车换行?

也许我们可以用下面的方式简单规避,输出单引号时,声明字符串用双引号括起来,像下面这样

str = "'"

同理,输出双引号时,声明字符串用单引号括起来,像下面这样

str = '"'

但是,这样会出现一个问题:如何同时显示单引号和双引号?这里就需要转义字符登场了。

转义字符用于表示不能直接显示的字符,比如后退键、回车键、等。

\ 开头的都是转义字符,下面时常用的转义字符格式:

转义字符 含义
\n 换行(LF),将当前位置移到下一行开头
\r 回车(CR),将当前位置移到本行开头
\\ 反斜杠字符\
\' 单引号
\" 双引号
\0 空字符(NULL)
\ddd 1到3位八进制数所代表的任意字符
\xhh 1到2位十六进制所代表的任意字符

例如,如果我们想给str赋值一个单引号,一个双引号('"),那么我们可以这样写:

str = "\'\""

下面需要你来完成一个简单的任务:

新建一个变量str,给str赋值为

ab\cd"ef'g\h]]

并打印出来

str = --补全代码
print(str)
--输出应为ab\cd"ef'g\h]]

string拼接

字符串和字符串可以相加吗?可以!我们可以用拼接符号来将两个独立的字符串拼起来。

我们使用..来表示字符串拼接符号,如下面的示例代码:

print('abc'..'def')
str1 = '123'
str2 = '999'
print(str1..str2)

下面你要完成这个任务:

已知三个字符串变量s1s2s3

请将他们按顺序拼接起来,存入all,并使用print输出结果

s1,s2,s3 = "aaa","bbb","ccc"
all = --请补全代码
print(all)

number转string

上面一节学习了如何拼接字符串,这个方法固然很好用,但是有时候我们会遇到一个需求,那就是把number类型的变量和string类型的变量拼接起来,组成一个新的string

比如下面的变量ns,如何拼接起来呢?

n = 123
s = 'm/s'

我们可以直接将number类型的变量n转换成string类型的值,这样就可以拼接了

使用tostring(value)函数即可实现这一操作:

n = 123
s = 'm/s'

result = tostring(n)..s
print(result)

下面你要完成这个任务:

已知三个变量n1sn2

然后将他们按顺序拼接起来,存入变量result,使输出结果正确

小提示:在某些情况下,Lua会自动将number类型转换成string类型

n1,s,n2 = 1,"abc",2
result = --请补全代码
print(result)

string转number

上面一节学习了如何将number转成string,这一节我们来学习如何将string转成number

比如下面的变量s,存储的内容是一个字符串,但是代表了一个数字,如何转成number再与n相加计算呢?

n = 123
s = '2333'

我们可以直接将string类型的变量s转换成number类型的值,这样就可以计算了

使用tonumber(value)函数即可实现这一操作:

n = 123
s = '2333'

result = tonumber(s) + n
print(result)

下面你要完成这个任务:

已知三个字符串变量s1s2s3,其内容均为纯数字

请计算他们的算术和,赋值给新建的变量result,使下面代码输出正确结果

s1,s2,s3 = "11","12","100"
result = --请补全代码
print(result)

逻辑运算

布尔型和比较运算

布尔型(boolean)只有两个可选值:true(真) 和 false(假)

Lua 把 false 和 nil 看作是false,其他的都为true(包括0这个值,也是相当于true

Lua 中也有许多的关系运算符,用于比较大小或比较是否相等,符号及其含义如下表:

符号 含义
== 等于,检测两个值是否相等,相等返回 true,否则返回 false
~= 不等于,检测两个值是否相等,相等返回 false,否则返回 true
> 大于,如果左边的值大于右边的值,返回 true,否则返回 false
< 小于,如果左边的值大于右边的值,返回 false,否则返回 true
>= 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false
<= 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false

我们可以通过以下实例来更加透彻的理解关系运算符的应用:

a = 21
b = 10
print('==的结果',a==b)
print('~=的结果',a~=b)
print('>的结果',a>b)
print('<的结果',a<b)
print('>=的结果',a>=b)
print('<=的结果',a<=b)

下面问题来了,运行以下代码,将会输出什么结果?请自行思考

a = 1
b = '1'
c = a
d = 2

print(a == b)
print(c == a)
print(a ~= b)
print(d <= c)

逻辑运算符

逻辑运算符基于布尔型的值来进行计算,并给出结果,下表列出了 Lua 语言中的常用逻辑运算符:

符号 含义
and 逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B
or 逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B
not 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false

我们可以通过以下实例来更加透彻的理解逻辑运算符的应用:

print('true and false的结果',true and false)
print('true or false的结果',true or false)
print('true and true的结果',true and true)
print('false or false的结果',false or false)
print('not false的结果',not false)
print('123 and 345的结果',123 and 345)
print('nil and true的结果',nil and true)

下面问题来了,运行以下代码,将会输出什么结果?

a = 1
b = '1'
c = 0

print(a and b)
print(c or a)
print(not b)
print(d and c)
print(1 < 2 and 3 > 2)

检验大小(自测题)

题目:如果已知number变量n,那么如果需要判断n是否符合下面的条件:

3<n≤10

以下四行判断代码,正确的是?

(返回true即表示变量n符合要求)

n = 1--这个数可能是任意数字
print(n > 10 or n <= 3)
print(n <= 10 or n > 3)
print(n < 3 and n >= 10)
print(n <= 10 or n > 3)

分支判断

条件判断

上面一节学习了布尔类型,那么这个需要用到哪里呢?我们需要用它来进行某些判断。

在Lua中,可以使用if语句来进行判断,如下面所举例的代码,可以判断n是否为小于10的数

n = 5
if n < 10 then
    print('n小于10')
end

我们整理一下,实际上if语句就是如下结构:

if 条件 then
    符合条件的代码
end

下面是你需要完成的事:

已知变量n,请判断n是否为奇数,如果是,请给n的值加上1

如果你觉得有难度,请查看下面的提示:

  • 求出n除以2的余数:n % 2
  • n的值加上1:n = n + 1
--已知一个number变量n
n = 5   --这个数字是几都有可能
print("n在判断前的值",n)

if 你的条件 then
    要做的事
end
print("n在判断后的值",n)

多条件判断

上面一节学习了简单的if语句写法,这一节我们来学习多条件分支语句

在Lua中,可以使用if语句来进行判断,同时可以使用else语句,表示多个分支判断

if 条件1 then
    满足条件1
elseif 条件2 then
    不满足条件1,但是满足条件2
else
    前面条件全都不满足
end

举个例子,比如有一个数字n

  • 当它大于等于0、小于5时,输出太小
  • 当它大于等于5、小于10时,输出适中
  • 当它大于等于10时,输出太大

那么代码就像如下这样:

n = 1--更改这个数多运行几次试试
if n >= 0 and n < 5 then
    print('太小')
elseif n >= 5 and n < 10 then
    print('适中')
elseif n >= 10 then
    print('太大')
end

注意:elseelseif都是可选的,可有可无,但是end不能省略

下面是你需要完成的事:

已知变量n,请判断n是否为奇数,

  • 如果是,请给n的值加上1
  • 如果不是,请将n的值改为原来的两倍
--已知一个number变量n
n = 5   --这个数字是几都有可能
print("n在判断前的值",n)

if 你的条件 then
    要做的事
else
    要做的事
end
print("n在判断后的值",n)

判断三角形合法性(自测题)

你需要使用前面几章的知识,来完成下面的题目

已知三个number类型的变量a、b、c,分别代表三根木棒的长度

请判断,使用这三根木棒,是否可以组成一个三角形(两短边之和大于第三边)

  • 如果可以组成,就打印出true
  • 如果不可以,就打印false
a,b,c = 1,2,3--多改几个数值自行测试

--补全代码

if的判断依据(自测题)

我们在前面了解到,Lua 把 falsenil 看作是false,其他的都为true(包括0这个值,也是相当于true

那么问题来了,执行下面的代码,将会输出什么?

result = ''
if 0 then
    result = result..'T,'
else
    result = result..'F,'
end
if a then
    result = result..'T'
else
    result = result..'F'
end
print(result)

函数

初识函数

函数是指一段在一起的、可以做某一件事儿的程序,也叫做子程序。

在前面的内容中,我们已经接触过了函数的调用,这个函数就是前面用到了很多次的print(...)

调用函数只需要按下面的格式即可:

函数名(参数1,参数2,参数3,......)

为何要使用函数?因为很多事情都是重复性操作,我们使用函数,可以快速完成这些操作

下面我们举一个最简单的函数例子,这个函数没有传入参数、没有返回值

它实现了一个简单的功能,就是输出Hello world!

function hello()
    print('Hello world!')
end

这个函数名为hello,我们可以按下面的方法进行调用(执行):

function hello()
    print('Hello world!')
end
hello()

这行代码会输出Hello world!

同时,在Lua中,函数也是一种变量类型,也就是说,hello实际上也是一个变量,里面存储的是一个函数,我们可以用下面的代码来理解:

function hello()
    print('Hello world!')
end
a = hello
--把hello函数同时赋值给a变量
a()
hello()
--a和hello变量指向同一个函数
--所以执行结果和hello()相同

因为函数只是个变量,你甚至在一开始可以这样声明hello函数:

hello = function()
    print('Hello world!')
end
hello()

下面你需要做一件简单的事情:

  • 新建一个函数变量biu,使其执行后会打印biubiubiu这个字符串,
  • 新建一个函数变量pong,使其与biu指向的函数相同
--请在此处补全代码

--请使下面的调用可以正常打印出biubiubiu
biu()
pong()
print("biu和pong相等吗?",biu==pong)

local变量

之前我们创建的变量,都是全局变量,这种变量在代码运行周期从头到尾,都不会被销毁,而且随处都可调用。

但是当我们代码量增加,很多时候大量新建全局变量会导致内存激增,我们需要一种可以临时使用、并且可以自动销毁释放内存资源的变量,要怎么解决呢?

我们可以使用local标志来新建临时变量,使用local创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。

参考下面的代码:

a = 123
function add()
    local n = a+2
    print(n)
end
add()
print(n)

上面的代码中,n就是一个局部变量,它只在这个funcion中有效,并且函数运行完后会自动回收这部分的内存。

我们应该尽可能的使用局部变量,以方便lua虚拟机自动回收内存空间,同时减少资源占用提高运行速度。

下面请阅读以下代码,思考一下,正确的输出结果是什么:

str = 'abc'
function connect()
    local s = str..'def'
end
print(s,str)

函数参数

在前几章的使用中,我们知道函数是可以传入参数的,如print(123)

那么,我们如何编写可以传入参数的函数呢?可以按下面的模板来写

function 函数名(参数1,参数2,...)
    代码内容
end

这里传入的参数,等价于在函数内部新建了一个local的变量,修改这些数据不会影响外部的数据(除了后面还没有讲到的table等类型)

举个例子,比如下面的函数,可以实现打印出两个传入值的和:

function add(a,b)
    print(a+b)
end
add(1,2)
--会输出3

这段代码其实等价于:

function add()
    local a = 1
    local b = 2
    print(a+b)
end
add()

下面问题来了,请设计一个函数p,可以按下面的调用方式来打印出物体的密度:

--补全这个函数的代码,满足题目要求
function p(a,b,c,m)

    print()
end

--一个长方体的长宽高分别为a、b、c(单位米)
a = 1
b = 2
c = 3
--这个物体重量为m(单位克)
m = 230
--下面打印出密度
--注:密度计算公式 密度 = 质量 / 体积
p(a,b,c,m)

函数返回值

在前面的代码中,我们实现了一个函数,输入变量ab,函数会自动输出两个数值的和。

但是一般来说,我们的需求远远不止这些,我们可能需要一个如下功能的函数:

执行函数,输入两个值,获取这两个值的和

如果还是按上面几节的内容,我们只会输出这个值,并不能把这个值传递给其他的变量进行后续使用,如何解决这个需求呢?

我们可以使用函数的返回值来实现这个需求,结合上面的需求,我们可以用下面的代码实现:

function add(a,b)
    return a+b
end
all = add(1,2)
--这里all的值就是3了
print(all)

这里的return表示返回一个值,并且立刻结束这个函数的运行

同时,和输入值可以有多个一样,返回值也可以有多个

function add(a,b)
    return a+b,"ok"
end
all, result = add(1,2)
--这里all的值就是3了
--这里result的值就是string "ok"
print(all,result)

下面问题来了,请设计一个函数p,可以按下面的调用方式来返回出物体的密度,返回值为number类型:

function p(a,b,c,m)
    --请补全代码
end

--一个长方体的长宽高分别为a、b、c(单位米)
a = 1
b = 2
c = 3
--这个物体重量为m(单位克)
m = 230
--下面返回密度值
--注:密度计算公式 密度 = 质量 / 体积
result = p(a,b,c,m)
print(result)

判断三角形合法性2(自测题)

你需要使用前面几章的知识,来完成下面的题目

  • 已知三个number类型的变量,分别代表三根木棒的长度
  • 请判断,使用这三根木棒,是否可以组成一个三角形(两短边之和大于第三边)
  • 请新建一个函数triangle,并可以用如下形式调用(如果可以组成,就返回true):
function triangle(a,b,c)
    --请补全代码
end

result = triangle(1,2,3)--传入值为三边长度,多改几个测试下
print(result)

返回多个值(自测题)

你需要使用前面几章的知识,来完成下面的题目

  • 已知2个number类型的变量,分别代表一个长方体的长和宽
  • 请计算这个长方形的周长和面积
  • 请新建一个函数rectangle,并可以用如下形式调用:
function rectangle(a,b)
    --补全代码
end

area,len = rectangle(1,2)
--结果:
--面积area为2
--周长len为6
print(area,len)

table

认识数组

数组,使用一个变量名,存储一系列的值

很多语言中都有数组这个概念,在Lua中,我们可以使用table(表)来实现这个功能

在Lua中,table是一个一系列元素的集合,使用大括号进行表示,其中的元素之间以逗号分隔,类似下面的代码:

t = {1,3,8,5,4}

我们可以直接使用元素下标,来访问、或者对该元素进行赋值操作。

在上面的table变量t中,第一个元素的下标是1,第二个是2,以此类推。

我们可以用变量名+中括号,中括号里加上下标,来访问或更改这个元素,如下面的例子:

t = {1,3,8,5,4}
print(t[1]) --打印1
print(t[3]) --打印8

t[2] = 99 --更改第二个元素的值
print(t[2]) --打印99

t[6] = 2 --凭空新建第六个元素并赋值
print(t[6]) --打印2

print(t[10])
--因为不存在,打印nil

以上就是table最简单的一个例子了,就是当作数组来用(注意,一般语言中的数组基本都为不可变长度,这里的table为可变长度)

下面你需要完成:

  • 新建一个table,名为cards,存入1-10十个数字
  • 将第3个元素与第7个元素交换
  • 将第9个元素与第2个元素交换
  • 增加第11个变量,值为23
--请补全代码
cards =

简单table

上一节里,我们将table来表示数组,实际上,table中可以包括任意类型的数据

比如我们可以在table中放置numberstring数据,类似下面的代码:

t = {"abc",223,",..a",123123}

我们甚至能在里面放function变量

t = {
    function() return 123 end,
    function() print("abc") end,
    function(a,b) return a+b end,
    function() print("hello world") end,
}
t[1]()
t[2]()
t[3]()
t[4]()

这些table访问每个元素的方式仍然是直接用下标,并且也能用下标来进行修改

下面你需要完成:

  • 新建一个table,名为funcList,并实现以下功能
  • 调用funcList[1](a,b),返回a和b的乘积
  • 调用funcList[2](a,b),返回a减b的差
  • 调用funcList[3](a),返回a的相反数(-a)
--请补全代码
funcList = {

}

a,b = 1,2--提供两个数
print("a,b值为",a,b)
print("a和b的乘积:",funcList[1](a,b))
print("a和b的差:",funcList[2](a,b))
print("a和相反数:",funcList[3](a))

table下标

在前两节,我们的table都只是一些简单的List(列表),每个元素的下标都是自动从1排列的

实际上,Lua中,下标可以直接在声明时进行指定,像下面这样:

t = {6,7,8,9}
--上面和下面的代码等价
t = {
    [1] = 6,
    [2] = 7,
    [3] = 8,
    [4] = 9,
}

--甚至你可以跳过某些下标
t = {
    [1] = 6,
    [3] = 7,
    [5] = 8,
    [7] = 9,
}
print(t[7])
--输出9

--在声明后赋予元素值也是可以的
t = {}--空的table
t[101] = 10
print(t[101])
--输出10

下面你需要:

  • 新建一个变量t,并按下面的格式声明
  • 下标为1的元素,值为123(number)
  • 下标为13的元素,值为"abc"(string)
  • 下标为666的元素,值为"666"(string)
--请补全代码
t = {

}

print("下标为1的元素:",t[1],type(t[1]))
print("下标为13的元素:",t[13],type(t[13]))
print("下标为666的元素:",t[666],type(t[666]))

下标进阶

在上一节,我们学习了如何自定义下标,其实在Lua中,下标也可以是字符串,如下面的例子

t = {
    ["apple"] = 10,
    banana = 12,
    pear = 6,
}
--使用["下标"] = 值
--和  下标 = 值
--都是正确写法
--当第二种方式有歧义时,应该用第一种方式

--可以用下面两种方式访问:
print(t["apple"])
--输出10
print(t.apple)
--输出10
--当第二种方式有歧义时,应该用第一种方式

可见,在使用string作为下标时,table的灵活性提升了一共数量级。

string作为下标时,也可以动态赋值:

t = {} -- 空table
t["new"] = "新的值"
print(t.new)
--输出 新的值

下面你需要完成:

  • 新建table变量t
  • 下标为apple的元素,值为123(number)
  • 下标为banana的元素,值为"abc"(string)
  • 下标为1@1的元素,值为"666"(string)
--请补全代码
t = {

}

print("下标为apple的元素:",t["apple"],type(t["apple"]))
print("下标为banana的元素:",t["banana"],type(t["banana"]))
print("下标为1@1的元素:",t["1@1"],type(t["1@1"]))

table小测验

下面的代码,将会打印什么?

t = {
    apple = {
        price = 7.52,
        weight = 2.1,
    },
    banana = {
        price = 8.31,
        weight = 1.4,
        year = '2018'
    },
    year = '2019'
}
print(
    t.price,
    t.apple.price,
    t.banana.weight,
    t.year
)

table小测验2

下面的代码,将会打印什么?

t = {
    {
        price = 7.52,
        weight = 2.1,
    },
    {
        price = 8.31,
        weight = 1.4,
        year = '2018'
    },
    year = '2019'
}
print(
    t["price"],
    t[1].price,
    t[2].weight,
    t["year"]
)

Lua全局变量与table

在前面我们知道了,在table中,可以直接用table名[下标]table名.string下标来访问元素

实际上,在Lua中,所有的全局变量全部被存放在了一共大table中,这个table名为:_G

我们可以用下面的例子来示范:

n = 123--新建变量
print(n)--输出123
print(_G.n)--输出123

_G.abc = 1--相当于新建全局变量
print(abc)--输出1

_G["def"] = 23--相当于新建全局变量
print(def)--输出23

--甚至你可以像下面这样
_G.print("hello")
_G["print"]("world")

现在,你明白为什么说万物基于table了吧?

你需要完成下面的任务:

  • 已知有一个全局变量,名为@#$
  • 请新建一个变量result
  • @#$变量里的值赋值给result
_G["@#$"] = 123

result = --请补全代码
print("result值为",result)

table小测试3

请新建一个名为t的table,满足以下要求

  • t[10]可获得number类型数据100
  • t.num可获得number类型数据12
  • t.abc[3]可获得string类型数据abcd
  • t.a.b.c可获得number类型数据789
--请补全代码
t = {

}

print("t[10]可获得number类型数据100:",t[10],type(t[10]))
print("t.num可获得number类型数据12:",t.num,type(t.num))
print("t.abc[3]可获得string类型数据abcd:",t.abc[3],type(t.abc[3]))
print("t.a.b.c可获得number类型数据789:",t.a.b.c,type(t.a.b.c))

table.concat

table.concat (table [, sep [, i [, j ] ] ])

将元素是string或者number类型的table,每个元素连接起来变成字符串并返回。

可选参数sep,表示连接间隔符,默认为空。

ij表示元素起始和结束的下标。

下面是例子:

local a = {1, 3, 5, "hello" }
print(table.concat(a))
print(table.concat(a, "|"))

-->打印的结果:
--135hello
--1|3|5|hello

请完成下面的任务:

  • 已知table变量t,
  • 将t中的结果全部连起来
  • 间隔符使用,
  • 并使用print打印出来
t = {"a","b","c","d"}
print("连接结果:")
--补全代码

table删减

table.insert (table, [pos ,] value)

在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。pos 的默认值是表的长度加一,即默认是插在表的最后。

table.remove (table [, pos])

在表 table 中删除索引为 pos(pos 只能是 number 型)的元素,并返回这个被删除的元素,它后面所有元素的索引值都会减一。pos 的默认值是表的长度,即默认是删除表的最后一个元素。

下面是例子:

local a = {1, 8}             --a[1] = 1,a[2] = 8
table.insert(a, 1, 3)   --在表索引为1处插入3
print(a[1], a[2], a[3])
table.insert(a, 10)    --在表的最后插入10
print(a[1], a[2], a[3], a[4])

-->打印的结果:
--3    1    8
--3    1    8    10


local a = { 1, 2, 3, 4}
print(table.remove(a, 1)) --删除速索引为1的元素
print(a[1], a[2], a[3], a[4])

print(table.remove(a))   --删除最后一个元素
print(a[1], a[2], a[3], a[4])

-->打印的结果:
--1
--2    3    4    nil
--4
--2    3    nil    nil

请完成下面的任务:

  • 已知table变量t
  • 去除t中的第一个元素
  • 然后这时,在t的第三个元素前,加上一个number变量,值为810
t = {1,2,3,4,5,6,7,8,9}

--补全代码

print("第一个元素应为2:",t[1])
print("第三个元素应为810:",t[3])

循环

while循环

在实际功能实现中,经常会遇到需要循环运行的代码,比如从1到100填充table数据,我们可以直接用循环语句来实现

我们首先来学习while这个循环语法,整体的格式如下:

while 继续循环判断依据 do
    执行的代码
end

下面举一个例子,我们计算从1加到100的结果:

local result = 0
local num = 1

while num <= 100 do
    result = result + num
    num = num + 1
end

print(result)

上面的代码,就是当num≤100时,result不断地加num,并且num每次循环后自己加1

理解了上面的代码,我们来完成下面一个简单的任务吧:

  • 已知两个number类型的变量minmax
  • 请计算从minmax之间,所有3的倍数的和
  • 打印出结果
min,max = 114,514 --这个结果应为42009
result = 0--结果存放到这个变量

while 请完善 do
    --补全代码
end

print("结果:",result)

for循环

for循环在某些程度上,和while循环很相似,但是for循环可以更加简洁地表达中间累积的量

我们首先来学习for这个循环语法,整体的格式如下:

for 临时变量名=开始值,结束值,步长 do
    循环的代码
end

其中,步长可以省略,默认为1

临时变量名可以直接在代码区域使用(但不可更改),每次循环会自动加步长值,并且在到达结束值后停止循环

下面举一个例子,我们计算从1加到100的结果:

local result = 0

for i=1,100 do
    result = result + i
end

print(result)

上面的代码,就是当i≤100时,result不断地加i,并且i每次循环后增加1

理解了上面的代码,我们来完成下面一个简单的任务吧:

  • 已知两个number类型的变量minmax
  • 请计算从minmax之间,所有7的倍数的和
  • 打印出结果
min,max = 114,514 --这个结果应为17955
result = 0--结果存放到这个变量

for --补全代码


print("结果:",result)

中断循环

前面我们学习了循环语句,有些时候循环运行到一半,我们不想再继续运行了,怎么办呢?

我们可以在一个循环体中使用break,来立即结束本次循环,继续运行下面的代码

比如像下面这样,计算1-100相加途中,小于100最大的和

result = 0
for i=1,100 do
    result = result + i
    if result > 100 then
        result = result - i
        break
    end
end

print(result)

可以看见,当发现和大于100后,代码立即把result的值还原到了加上当前数字之前的状态,并且调用break语句,立即退出了本次循环

while中,我们也可以使用break:

result = 0
c = 1
while true do
    result = result + c
    c = c + 1
    if result > 100 then
        result = result - i
        break
    end
end

print(result)

我们在这里直接使用了死循环(因为while的继续运行判断依据始终为true),整体逻辑也和之前for的代码一致,当发现和大于100后,代码立即把result的值还原到了加上当前数字之前的状态,并且调用break语句,立即退出了本次循环

现在你需要完成一项任务:

  • 请求出大于变量max的,最小的13的倍数(max大于0)
  • 并将结果打印出来
  • 本题理论上不用循环就能实现,但是为了练习一下技巧,请用for循环来实现
max = 810 --结果应为806
result = 0

for --请补全代码

print(result)

循环测试题1(自测题)

前面我们学习了循环语句,我们需要完成下面的任务

我们知道,print函数可以打印一行完整的输出

那么,已知变量a,请打印出下面的结果:

(a为大于0的整数,且需要输出a行数据,数据从1开始,每行与上一行的差为2)

1
3
5
7
9
(上面例子为当a为5的情况)

做题区域:

a = 10
--需要用print输出要求的结果
print("输出结果:")


for --请补全代码

循环测试题2(自测题)

我们需要完成下面的任务

那么,已知变量a,请打印出下面的结果:

(a为大于0的整数,且需要输出a行数据,第一行为一个,后面每行多一个

*
**
***
****
*****
(上面例子为当a为5的情况)

做题区域:

a = 10
--需要用print输出要求的结果
print("输出结果:")


for --请补全代码

循环测试题3(自测题)

我们需要完成下面的任务

那么,已知变量a,请打印出下面的结果:

(a为大于0的整数,且需要输出a行数据,按图示规律输出)

1
12
123
1234
12345
123456
1234567
12345678
123456789
12345678910
1234567891011
(上面例子为当a为11的情况)

做题区域:

a = 20
--需要用print输出要求的结果
print("输出结果:")


for --请补全代码

循环测试题4(自测题)

  • 有一只猴子,第一天摘了若干个桃子 ,当即吃了一半,但还觉得不过瘾 ,就又多吃了一个。
  • 第2天早上又将剩下的桃子吃掉一半,还是觉得不过瘾,就又多吃了两个。
  • 以后每天早上都吃了前一天剩下的一半加天数个(例如,第5天吃了前一天剩下的一半加5个)。
  • 到第n天早上再想吃的时候,就只剩下一个桃子了。
  • 那么,已知变量a为最后一天的天数,请打印出第一天的桃子数。
  • 如:a为5时,输出114

做题区域:

a = 6
--需要用print输出要求的结果
print("输出结果:")


for --请补全代码

详解string库

string.sub

接下来几节会讲解string库的各种接口

string.sub(s, i [, j])

返回字符串 s 中,从索引 i 到索引 j 之间的子字符串。

i 可以为负数,表示倒数第几个字符。

当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。

当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。

下面是例子:

print(string.sub("Hello Lua", 4, 7))
print(string.sub("Hello Lua", 2))
print(string.sub("Hello Lua", 2, 1))
print(string.sub("Hello Lua", -3, -1))

-->打印的结果:
lo L
ello Lua

Lua

值得注意的是,我们可以使用冒号来简化语法,像下面这样:

s = "12345"
s1 = string.sub(s, 4, 7)
s2 = s:sub(4, 7)
--两种写法是等价关系
print(s1,s2)

请完成下面的任务:

  • 已知字符串变量s,请分别打印出(每种一行):
  • s第4个字符开始,到最后的值
  • s第1个字符开始,到倒数第3个字符的值
  • s倒数第5个字符开始,到倒数第2个字符的值
s = "1919810"

--补全代码
print()
print()
print()

string.rep

string.rep(s, n)

返回字符串 s 的 n 次拷贝。

示例代码:

print(string.rep("abc", 3))
--输出结果:
--abcabcabc

请完成下面的任务:

打印一行数据,数据内容为810个114514

--补全代码
print()

string.len

string.len(s)

接收一个字符串,返回它的长度。

示例代码:

s = "hello lua"
print(string.len(s))
--输出结果:
9

--同时也可以使用简便语法
print(s:len())

请完成下面的任务:

  • 新建一个变量s,使数据内容为810个114514
  • 并打印出字符串s的长度
s = --补全代码
print()

大小写转换

string.lower(s)

接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。

string.upper(s)

接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。

示例代码:

s = "hello lua"
print(string.upper(s))
print(string.lower(s))
--输出结果:
HELLO LUA
hello lua

--同时也可以使用简便语法
print(s:upper())
print(s:lower())

请完成下面的任务:

已知一个变量s,打印出全是大写字母的s字符串

s = "asd8938KJjsidiajdl;(()k)"
print --补全代码

string.format

string.format(formatstring, ...)

按照格式化参数formatstring,返回后面...内容的格式化版本。

编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:

它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,及如何放入它们。

一个指示由字符%加上一个字母组成,这些字母指定了如何格式化参数,例如d用于十进制数、x用于十六进制数、o用于八进制数、f用于浮点数、s用于字符串等。

示例代码:

print(string.format("%.4f", 3.1415926))     -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d,m,y = 29,7,2015
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))
--控制输出2位数字,并在前面补0

-->输出
-- 3.1416
-- 31 1f 37
-- today is: 29/07/2015

请完成下面的任务:

  • 已知一个变量n,为number类型整数
  • 打印出n:连上n值的字符串
n = 810

print --补全代码

string的本质

这一节我们来讲解字符串的本质

字符串,是用来存储一串字符的,但是它的本质就是一串数字。如何也一串数字来代表一串字符呢?

在计算机中,每一个符号都对应着一个数字,但是在讲解这个知识之前,我们了解一下补充知识:

在大多数编程语言中,我们使用0x开头来表示这个数字是16进制的。
比如
10等价于0x0a
256等价于0xff

接下来,你需要了解,每一个符号都对应着一个数字,比如:

0对应着0x301对应着0x31 a对应着0x61b对应着0x62 A对应着0x41B对应着0x42

上面的编码规则,我们称之为ascii码,具体想了解可以打开下面的网址查看:

http://ascii.911cha.com/

当然,1字节最大为0xff,即256,只能存下一部分符号,大部分的中文按某些编码,一个中文占用2或3个字节

计算机如何解析这些数据,我们不需要了解,当你知道了上面的知识后,你应该可以理解下面的描述:

字符串"apple"实际上的内容就是下面的一串数字:
0x61,0x70,0x70,0x6c,0x65

同时,lua的字符串中可以保存任何数值,即使是0x00这种不代表任何含义的数,也可以保存

补充:在其他语言中(如C),0x00代表字符串结束,但是在lua中并不是这样。
lua的字符串每字节可以存储任意的一字节数据。

比如下面的描述:

有一串lua字符串中的数据为:
0x01,0x02,0x30,0x00,0x44
实际人能看到的(不可见字符用�代替):
��0�D
当然,你不能说你看不见的数据就不存在,他们都完好无损地在这个字符串中

下面你需要思考一个问题:一串字符串数据如下,它的实际内容是什么(指人能看见的字符串内容,如abcd)?

0x62,0x61,0x6e,0x61,0x6e,0x61


string.char

string.char (...)

接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。当参数为空时,默认是一个 0。

如果上一章节有认真学习过了的话,这段话应该是很好理解的。实质上就是把计算机认识的一串数字,变成字符串变量,并且字符串内的数据就是要存的那串数据。

示例代码:

str1 = string.char(0x30,0x31,0x32,0x33)
str2 = string.char(0x01,0x02,0x30,0x03,0x44)
print(str1)
print(str2)

-->输出(不可见字符用�代替)
--0123
--��0�D

请完成下面的任务:

  • 已知一个字符串的每个字符在数组t中按顺序排列
  • 请根据t的值,打印出字符串内容(一行数据)
  • 注:这个字符串存储的不一定是可见的字符
t = {0x79,0x6F,0x75,0x20,0x61,0x72,0x65,0x20,0x72,0x69,0x67,0x68,0x74}
print("真正的字符串内容:")
--补全代码

string.byte

string.byte(s [, i [, j ] ])

返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。i 的默认值为 1,即第一个字节,j 的默认值为 i 。

这个函数功能刚好和前面的string.char相反,是提取字符串中实际的数值。

示例代码:

str = "12345"
print(string.byte(str,2))
print(str:byte(2))--也可以这样
print(str:byte())--不填默认是1

-->输出(十进制数据)
--50
--50
--49

请完成下面的任务:

  • 已知字符串s
  • 请把s中代表的数据,全部相加,并打印出来
s = string.char(1,2,3,4,5,6,7,8,9)
print("s内数据的和是:")
--补全代码

string.find

string.find(s, p [, init [, plain] ])

这个函数会在字符串s中,寻找匹配p字符串的数据。如果成功找到,那么会返回p字符串在s字符串中出现的开始位置和结束位置;如果没找到,那么就返回nil

第三个参数init默认为1,表示从第几个字符开始匹配,当init为负数时,表示从s字符串的倒数第-init个字符处开始匹配。

第四个参数plain默认为false,当其为true时,只会把p看成一个字符串对待。

可能你会奇怪,第四个参数有什么存在的必要吗?p不是本来就应该是个字符串吗? 实际上,lua中的匹配默认意义是正则匹配,同时,这里的正则与其它语言也有些许不同。

由于篇幅有限,本节和下面的几节涉及匹配内容时,均不会考虑正则的使用方法,Lua正则教程将会在最后几节单独详细地列出来。

第四个参数为true时,便不会使用正则功能。

示例代码:

--只会匹配到第一个
print(string.find("abc abc", "ab"))
-- 从索引为2的位置开始匹配字符串:ab
print(string.find("abc abc", "ab", 2))
-- 从索引为5的位置开始匹配字符串:ab
print(string.find("abc abc", "ab", -3))

-->输出
--1  2
--5  6
--5  6

请完成下面的任务:

  • 已知字符串s,里面有很多相同的字符串
  • 请找出字符串s中,所有字符串awsl的位置
  • 使用print打印结果,结果一行一个
  • 如字符串12awslawslaw,输出3和7
s = "12awsaslwlaawsllslllswasllalssawwlawslaw"
print("两个awsl的位置分别是:")
--补全代码

string.gsub

string.gsub(s, p, r [, n])

将目标字符串s中所有的子串p替换成字符串r。

可选参数n,表示限制替换次数。

返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。

特别提示:这个函数的目标字符串s,也是支持正则的

下面是例子:

print(string.gsub("Lua Lua Lua", "Lua", "hello"))
print(string.gsub("Lua Lua Lua", "Lua", "hello", 2)) --指明第四个参数

-->打印的结果:
-- hello hello hello   3
-- hello hello Lua     2

同样的,我们也可以使用冒号来简化语法,像下面这样:

s = "12345"
r = s:gsub("2","b")
print(r)

请完成下面的任务:

  • 已知字符串变量s,请分别打印出(每种一行):
  • 把字符串s中,前5个a,替换为b
  • 把字符串s中,前3个c,替换为xxx
  • 把结果打印出来,一行数据
s = "asdicagydausckfugdaflgscdabgsdbahhacbshbsd"
print("s变换前的值:",s)
--补全代码

正则匹配相关内容等待更新


跨文件调用

Lua5.1里的用法

目前模块使用Lua版本是5.1,由于模拟器的版本为5.3,这部分代码语法并不相同,所以这里不提供代码测试功能

在编写代码时,随着逻辑逐渐复杂,我们的代码量也会变大。虽然有函数可以把一部分代码逻辑封装起来,但是所有代码都放到一个文件里,显然也不是个好办法。

所以我们需要将一些代码放到不同文件中,通过文件来区分这些代码的功能。

比如我们有下面这个函数:

---函数功能:
-- 生成从1-max的table
-- @输入值:table的最大值
-- @返回:  table结果
-- @例子:  local list = getNumberList(10)
function getNumberList(max)
    local t = {}
    for i=1,max do
        table.insert(t,i)
    end
    return t
end

我们新建一个文件叫tools.lua,把这个函数放进去,现在,整个文件如下面这样:

tools.lua

--使这个代码在哪个文件里都能被调用
module(..., package.seeall)

---函数功能:
-- 生成从1-max的table
-- @输入值:table的最大值
-- @返回:  table结果
-- @例子:  local list = getNumberList(10)
function getNumberList(max)
    local t = {}
    for i=1,max do
        table.insert(t,i)
    end
    return t
end

现在,我们封装的这个函数就能在其他文件里被调用了,具体代码如下:

--引用tools.lua文件,并加载
require"tools"

local list = tools.getNumberList(12)

当调用了require接口后,Lua虚拟机会自动加载你调用的文件,执行文件的内容,并返回一个以文件名为变量名的table,该table内包含了那个文件里所有非local的变量和函数

为了更好地理解这段话,我们可以看下面两个文件,其中run.lua是被运行的那个入口文件

test.lua

--使这个代码在哪个文件里都能被调用
module(..., package.seeall)

a = 1
local b = 2
--local变量无法被外部调用
--但是可以在文件内被调用

c = a + b
--文件在被require的时候,会被执行

function addB()
    --文件内部可以调用变量b
    b = b + 1
    return b
end

run.lua

require"test"
print(test.a)--输出1
print(test.b)--输出nil,因为b是local变量
print(test.c)--输出3

print(test.addB())--输出3
print(test.addB())--输出4
print(test.addB())--输出5

同时,每个文件最多只会被require一次,如果有多个require,只有第一次会执行

Lua5.3里的用法

在编写代码时,随着逻辑逐渐复杂,我们的代码量也会变大。虽然有函数可以把一部分代码逻辑封装起来,但是所有代码都放到一个文件里,显然也不是个好办法。

所以我们需要将一些代码放到不同文件中,通过文件来区分这些代码的功能。

比如我们有下面这个函数:

---函数功能:
-- 生成从1-max的table
-- @输入值:table的最大值
-- @返回:  table结果
-- @例子:  local list = getNumberList(10)
function getNumberList(max)
    local t = {}
    for i=1,max do
        table.insert(t,i)
    end
    return t
end

我们新建一个文件叫tools.lua,把这个函数放进去,现在,整个文件如下面这样:

tools.lua

---函数功能:
-- 生成从1-max的table
-- @输入值:table的最大值
-- @返回:  table结果
-- @例子:  local list = getNumberList(10)
local function getNumberList(max)
    local t = {}
    for i=1,max do
        table.insert(t,i)
    end
    return t
end

--手动返回一个table,包含了上面的函数
return {
    getNumberList = getNumberList,
}

现在,我们封装的这个函数就能在其他文件里被调用了,具体代码如下:

--引用tools.lua文件,并加载
local tool = require("tools")

local list = tool.getNumberList(12)

当调用了require接口后,Lua虚拟机会自动加载你调用的文件,执行文件的内容,然后返回你文件里return的结果

为了更好地理解这段话,我们可以看下面两个文件,其中run.lua是被运行的那个入口文件

test.lua

--以便一会儿返回使用的table
local temp = {}

--把全局变量a更改了
a = 1

--local变量无法被外部调用
--但是可以在文件内被调用
local b = 2

--文件在被require的时候,会被执行
--把全局变量c更改了
c = a + b

--使函数在table里
function temp.addB()
    --文件内部可以调用变量b
    b = b + 1
    return b
end

--返回table
return temp

run.lua

local test = require("test")
print(a)--输出1
print(b)--输出nil,因为b是local变量
print(c)--输出3

print(test.addB())--输出3
print(test.addB())--输出4
print(test.addB())--输出5

同时,每个文件最多只会被require一次,如果有多个require,只有第一次会执行

待添加

  • table -> sort排序
  • string -> 正则、gsub、find、match
  • 迭代器 for xx in xx 、string.gmatch
  • 元表
  • coroutine

↓各种接口↓

  • math库
  • os库
  • io库
  • 位运算 (>> <<)
  • 杂项接口
  • debug库