217. lua
简介
初识Lua
Lua 是一种轻量小巧的脚本语言,它用标准C语言编写并以源代码形式开放。这意味着什么呢?这意味着Lua虚拟机可以很方便的嵌入别的程序里,从而为应用程序提供灵活的扩展和定制功能。而整个Lua虚拟机编译后仅仅一百余K,经过适当的裁剪还能做到更小,十分适合嵌入式的开发。
同时,在目前脚本引擎中,Lua的运行速度占有绝对优势。这些都决定了Lua是作为嵌入式脚本的最佳选择。
在我们编写代码之前,需要做一下准备,就是:确保你输入标点符号时,用的不是中文输入法。
第一个 Lua 程序
几乎所有语言的第一行代码,都是输出hello world
,本教程也不意外。
在Lua
中,打印结果只需要使用print
这个函数
即可。同时,如果需要使用函数
,只需要在函数名后加上双括号,同时传入你想传入的值即可。
所以,我们来执行下面的代码,打印出hello world
吧!
1 | print("hello world!") |
输出数据
上一部分,我们知道了,在Lua
中,可以使用print
函数来打印你想要得到的结果。
并且还知道了,函数
是指可以实现某些功能的子程序,可以使用函数名(参数)
来执行。
让我们试着输出一些其他东西吧!使用多个print函数,输出自己想输出的数据。
1 | print("测试") |
代码注释
代码注释
就是在代码里,不会运行的部分。注释
完全不会被运行。
这部分是为了在查看代码时,可以更好地立即现有代码含义用的。
我们可以用--
开头,来写一段单行注释
也可以用--[[
开头,]]
结尾,写一段多行注释。
下面是注释的例子:
1 | print("这段代码会运行") |
变量
数据类型
数据类型 | 描述 |
---|---|
nil | 是一个空值 |
boolea | false、true |
number | 表示双精度的浮点数 |
string | 字符串,由一堆双引号或者单引号包围 |
function | 由C或者Lua编写的函数 |
userdate | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线程,用于执行协同程序 |
table | Lua中的表是一个关联数组,数组的索引可以是数字或者字符串。最简单的表是{},表示是一个空表 |
number变量
变量
,可以看作是一个桶,在里面装你想要装的内容。这些内容可以是Lua包含的所有合法类型。
例如:我想要新建一个桶,名叫bucket,在里面放入233这个数字,就可以像下面一样:
1 | bucket = 233 |
让我们试着自己新建几个变量吧!
- 新建变量year,并将变量的值设置为1926
- 新建变量month,并将变量的值设置为8
- 新建变量day,并将变量的值设置为7
1 | --新建三个变量,并赋值 |
了解nil
nil
类型表示没有任何有效值,只要是没有声明的值,它就是nil
比如我打印一个没有声明的值,便会输出nil
:
1 | ccc = 233 |
这里需要你思考一下,运行以下代码,将会输出什么结果?
1 | a = 1 |
赋值语句
赋值
是改变一个变量值的最基本的方法。
如下面一样,使用等号
对左边
的变量
进行赋值
1 | n = 2 |
Lua
可以对多个变量同时赋值,变量用逗号
分开,赋值
语句右边的值
会依次赋给左边的变量
。
1 | n = 1 |
当左右值的数量不一致时,Lua会进行下面的设定:
- 变量个数 > 值的个数:按变量个数补足nil
- 变量个数 < 值的个数:多余的值会被忽略
下面的例子可以展示这种设定:
1 | a, b, c = 0, 1 |
这里需要你思考一下,运行以下代码,将会输出什么结果?
1 | a,b,c = 1,2,3 |
交换变量
这部分需要你自己完成一个任务:
已知下面的代码,并且已知a
和b
的值,请在交换他们的值,使打印输出12 34
1 | a = 34 |
输出变量
我们已经知道了,在Lua中,可以使用print函数来打印你想要得到的结果。
同时在上一节,我们学会了新建变量和设置变量的值。
让我们试着输出某个变量吧!使用print函数
,输出已知变量。 我们已知变量num
为某个数字,试着输出它的值吧!
1 | num = 123 |
算数运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。
上一节中,新建的数字变量,我们称之为number
类型的变量。
本节我们来学习使用算术运算符
,如下所示:
1 | + 加法 |
我们可以通过以下实例来理解算术运算符的应用:
1 | a = 21 |
你需要完成下面的任务:
已知,一个长方体的长宽高分别为a、b、c(单位米),且这个物体重量为m(单位克)
请打印出物体的密度(单位g/m³)
注:密度计算公式 密度 = 质量 / 体积
1 | a,b,c = 1,2,3 --长宽高 |
字符串
string类型变量
字符串
(即string
),就是一串文本数据,可以存储你要的文本。
在第二节中,print
出的数据就是一个字符串。
Lua 语言中字符串可以使用以下三种方式来表示:
- 单引号间的一串字符
- 双引号间的一串字符
- [[和]]间的一串字符
你可以参考下面的例子来深入理解:
1 | --双引号间的一串字符 |
接下来你需要完成下面的练习:
新建三个变量s1
、s2
、s3
分别存入字符串数据:str
、abc
、233
,使输出打印正确
1 | --请在空白处补全代码 |
转义字符
在上一节中,我们学习了如何声明字符串。
但是我们有时候会遇到一些特殊的问题,如:如何输出单引号和双引号?如何输出回车换行?
也许我们可以用下面的方式简单规避,输出单引号时,声明字符串用双引号括起来,像下面这样
1 | str = "'" |
同理,输出双引号时,声明字符串用单引号括起来,像下面这样
1 | str = '"' |
但是,这样会出现一个问题:如何同时显示单引号和双引号?这里就需要转义字符
登场了。
转义字符用于表示不能直接显示的字符,比如后退键、回车键、等。
以 \
开头的都是转义字符,下面时常用的转义字符格式:
转义字符 | 含义 |
---|---|
\n | 换行(LF),将当前位置移到下一行开头 |
\r | 回车(CR),将当前位置移到本行开头 |
\\ | 反斜杠字符\ |
' | 单引号 |
" | 双引号 |
\0 | 空字符(NULL) |
\ddd | 1到3位八进制数所代表的任意字符 |
\xhh | 1到2位十六进制所代表的任意字符 |
例如,如果我们想给str赋值一个单引号,一个双引号(’”),那么我们可以这样写:
1 | str = "\'\"" |
下面需要你来完成一个简单的任务:
新建一个变量str
,给str赋值为
ab\\c\"ef\'\\h
并打印出来
1 | str = --补全代码 |
string拼接
字符串和字符串可以相加吗?可以!我们可以用拼接符号来将两个独立的字符串拼起来。
我们使用..
来表示字符串拼接符号,如下面的示例代码:
1 | print('abc'..'def') |
下面你要完成这个任务:
已知三个字符串变量s1
、s2
、s3
请将他们按顺序拼接起来,存入all
,并使用print输出结果
1 | s1,s2,s3 = "aaa","bbb","ccc" |
number转string
上面一节学习了如何拼接字符串,这个方法固然很好用,但是有时候我们会遇到一个需求,那就是把number
类型的变量和string
类型的变量拼接起来,组成一个新的string
比如下面的变量n
和s
,如何拼接起来呢?
1 | n = 123 |
我们可以直接将number
类型的变量n
转换成string
类型的值,这样就可以拼接了
使用tostring(value)
函数即可实现这一操作:
1 | n = 123 |
下面你要完成这个任务:
已知三个变量n1
、s
、n2
然后将他们按顺序拼接起来,存入变量result
,使输出结果正确
小提示:在某些情况下,Lua会自动将number类型转换成string类型
1 | n1,s,n2 = 1,"abc",2 |
string转number
上面一节学习了如何将number转成string,这一节我们来学习如何将string转成number
比如下面的变量s
,存储的内容是一个字符串,但是代表了一个数字,如何转成number再与n相加计算呢?
1 | n = 123 |
我们可以直接将string
类型的变量s
转换成number
类型的值,这样就可以计算了
使用tonumber(value)
函数即可实现这一操作:
1 | n = 123 |
下面你要完成这个任务:
已知三个字符串变量s1
、s2
、s3
,其内容均为纯数字
请计算他们的算术和,赋值给新建的变量result
,使下面代码输出正确结果
1 | s1,s2,s3 = "11","12","100" |
逻辑运算
布尔型和比较运算
布尔型(boolean)只有两个可选值:true
(真) 和 false
(假)
**Lua 把 false 和 nil 看作是false
**,其他的都为true(包括0这个值,也是相当于true
)
Lua 中也有许多的关系运算符
,用于比较大小或比较是否相等,符号及其含义如下表:
符号 | 含义 |
---|---|
== | 等于,检测两个值是否相等,相等返回 true,否则返回 false |
~= | 不等于,检测两个值是否相等,相等返回 false,否则返回 true |
> | 大于,如果左边的值大于右边的值,返回 true,否则返回 false |
< | 小于,如果左边的值大于右边的值,返回 false,否则返回 true |
>= | 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false |
<= | 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false |
我们可以通过以下实例来更加透彻的理解关系运算符的应用:
1 | a = 21 |
下面问题来了,运行以下代码,将会输出什么结果?请自行思考
1 | a = 1 |
逻辑运算符
逻辑运算符基于布尔型的值来进行计算,并给出结果,下表列出了 Lua 语言中的常用逻辑运算符:
符号 | 含义 |
---|---|
and | 逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B |
or | 逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B |
not | 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false |
我们可以通过以下实例来更加透彻的理解逻辑运算符的应用:
1 | print('true and false的结果',true and false) |
下面问题来了,运行以下代码,将会输出什么结果?
1 | a = 1 |
检验大小(自测题)
题目:如果已知number变量n
,那么如果需要判断n是否符合下面的条件:
3<n≤10
以下四行判断代码,正确的是?
(返回true即表示变量n
符合要求)
1 | n = 1--这个数可能是任意数字 |
分支判断
条件判断
上面一节学习了布尔类型,那么这个需要用到哪里呢?我们需要用它来进行某些判断。
在Lua中,可以使用if
语句来进行判断,如下面所举例的代码,可以判断n是否为小于10的数
:
1 | n = 5 |
我们整理一下,实际上if语句就是如下结构:
1 | if 条件 then |
下面是你需要完成的事:
已知变量n
,请判断n
是否为奇数,如果是,请给n
的值加上1
如果你觉得有难度,请查看下面的提示:
- 求出
n
除以2的余数:n % 2 - 给
n
的值加上1:n = n + 1
1 | --已知一个number变量n |
多条件判断
上面一节学习了简单的if语句写法,这一节我们来学习多条件分支语句
在Lua中,可以使用if
语句来进行判断,同时可以使用else
语句,表示多个分支判断
1 | if 条件1 then |
举个例子,比如有一个数字n
:
- 当它大于等于0、小于5时,输出
太小
, - 当它大于等于5、小于10时,输出
适中
, - 当它大于等于10时,输出
太大
,
那么代码就像如下这样:
1 | n = 1--更改这个数多运行几次试试 |
注意:else
和elseif
都是可选的,可有可无,但是end
不能省略
下面是你需要完成的事:
已知变量n
,请判断n
是否为奇数,
- 如果是,请给
n
的值加上1 - 如果不是,请将
n
的值改为原来的两倍
1 | --已知一个number变量n |
判断三角形合法性(自测题)
你需要使用前面几章的知识,来完成下面的题目
已知三个number类型的变量a、b、c,分别代表三根木棒的长度
请判断,使用这三根木棒,是否可以组成一个三角形(两短边之和大于第三边)
- 如果可以组成,就打印出true
- 如果不可以,就打印false
1 | a,b,c = 1,2,3--多改几个数值自行测试 |
if的判断依据(自测题)
我们在前面了解到,Lua 把 false
和 nil
看作是false
,其他的都为true
(包括0
这个值,也是相当于true
)
那么问题来了,执行下面的代码,将会输出什么?
1 | result = '' |
函数
初识函数
函数
是指一段在一起的、可以做某一件事儿的程序,也叫做子程序。
在前面的内容中,我们已经接触过了函数的调用,这个函数就是前面用到了很多次的print(...)
。
调用函数只需要按下面的格式即可:
函数名(参数1,参数2,参数3,......)
为何要使用函数?因为很多事情都是重复性操作,我们使用函数,可以快速完成这些操作
下面我们举一个最简单的函数例子,这个函数没有传入参数、没有返回值
它实现了一个简单的功能,就是输出Hello world!
:
1 | function hello() |
这个函数名为hello
,我们可以按下面的方法进行调用(执行):
1 | function hello() |
这行代码会输出Hello world!
。
同时,在Lua中,函数也是一种变量类型,也就是说,hello
实际上也是一个变量,里面存储的是一个函数,我们可以用下面的代码来理解:
1 | function hello() |
因为函数只是个变量,你甚至在一开始可以这样声明hello
函数:
1 | hello = function() |
下面你需要做一件简单的事情:
- 新建一个函数变量
biu
,使其执行后会打印biubiubiu
这个字符串, - 新建一个函数变量
pong
,使其与biu
指向的函数相同
1 | --请在此处补全代码 |
local变量
之前我们创建的变量,都是全局变量,这种变量在代码运行周期从头到尾,都不会被销毁,而且随处都可调用。
但是当我们代码量增加,很多时候大量新建全局变量会导致内存激增,我们需要一种可以临时使用、并且可以自动销毁释放内存资源的变量,要怎么解决呢?
我们可以使用local
标志来新建临时变量,使用local
创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。
参考下面的代码:
1 | a = 123 |
上面的代码中,n
就是一个局部变量,它只在这个funcion
中有效,并且函数运行完后会自动回收这部分的内存。
我们应该尽可能的使用局部变量,以方便lua虚拟机自动回收内存空间,同时减少资源占用提高运行速度。
下面请阅读以下代码,思考一下,正确的输出结果是什么:
1 | str = 'abc' |
函数参数
在前几章的使用中,我们知道函数是可以传入参数的,如print(123)
那么,我们如何编写可以传入参数的函数呢?可以按下面的模板来写
1 | function 函数名(参数1,参数2,...) |
这里传入的参数,等价于在函数内部新建了一个local
的变量,修改这些数据不会影响外部的数据(除了后面还没有讲到的table
等类型)
举个例子,比如下面的函数,可以实现打印出两个传入值的和:
1 | function add(a,b) |
这段代码其实等价于:
1 | function add() |
下面问题来了,请设计一个函数p
,可以按下面的调用方式来打印出物体的密度:
1 | --补全这个函数的代码,满足题目要求 |
函数返回值
在前面的代码中,我们实现了一个函数,输入变量a
、b
,函数会自动输出两个数值的和。
但是一般来说,我们的需求远远不止这些,我们可能需要一个如下功能的函数:
执行函数,输入两个值,获取这两个值的和
如果还是按上面几节的内容,我们只会输出这个值,并不能把这个值传递给其他的变量进行后续使用,如何解决这个需求呢?
我们可以使用函数的返回值来实现这个需求,结合上面的需求,我们可以用下面的代码实现:
1 | function add(a,b) |
这里的return
表示返回一个值,并且立刻结束这个函数的运行
同时,和输入值可以有多个一样,返回值也可以有多个
1 | function add(a,b) |
下面问题来了,请设计一个函数p
,可以按下面的调用方式来返回出物体的密度,返回值为number
类型:
1 | function p(a,b,c,m) |
判断三角形合法性2(自测题)
你需要使用前面几章的知识,来完成下面的题目
- 已知三个number类型的变量,分别代表三根木棒的长度
- 请判断,使用这三根木棒,是否可以组成一个三角形(两短边之和大于第三边)
- 请新建一个函数triangle,并可以用如下形式调用(如果可以组成,就返回true):
1 | function triangle(a,b,c) |
返回多个值(自测题)
你需要使用前面几章的知识,来完成下面的题目
- 已知2个number类型的变量,分别代表一个长方体的长和宽
- 请计算这个长方形的周长和面积
- 请新建一个函数
rectangle
,并可以用如下形式调用:
1 | function rectangle(a,b) |
table
认识数组
数组,使用一个变量名,存储一系列的值
很多语言中都有数组这个概念,在Lua中,我们可以使用table
(表)来实现这个功能
在Lua中,table是一个一系列元素的集合,使用大括号进行表示,其中的元素之间以逗号分隔,类似下面的代码:
1 | t = {1,3,8,5,4} |
我们可以直接使用元素
的下标
,来访问、或者对该元素
进行赋值操作。
在上面的table
变量t
中,第一个元素的下标是1
,第二个是2
,以此类推。
我们可以用变量名
+中括号
,中括号里加上下标
,来访问或更改这个元素
,如下面的例子:
1 | t = {1,3,8,5,4} |
以上就是table最简单的一个例子了,就是当作数组来用(注意,一般语言中的数组基本都为不可变长度,这里的table为可变长度)
下面你需要完成:
- 新建一个table,名为cards,存入1-10十个数字
- 将第3个元素与第7个元素交换
- 将第9个元素与第2个元素交换
- 增加第11个变量,值为23
1 | --请补全代码 |
简单table
上一节里,我们将table
来表示数组
,实际上,table
中可以包括任意类型的数据
比如我们可以在table
中放置number
和string
数据,类似下面的代码:
1 | t = {"abc",223,",..a",123123} |
我们甚至能在里面放function
变量
1 | t = { |
这些table
访问每个元素的方式仍然是直接用下标,并且也能用下标来进行修改
下面你需要完成:
- 新建一个
table
,名为funcList
,并实现以下功能 - 调用
funcList[1](a,b)
,返回a和b的乘积 - 调用
funcList[2](a,b)
,返回a减b的差 - 调用
funcList[3](a)
,返回a的相反数(-a)
1 | --请补全代码 |
table下标
在前两节,我们的table
都只是一些简单的List(列表),每个元素的下标
都是自动从1排列的
实际上,Lua中,下标可以直接在声明时进行指定,像下面这样:
1 | t = {6,7,8,9} |
下面你需要:
- 新建一个变量
t
,并按下面的格式声明 - 下标为
1
的元素,值为123
(number) - 下标为
13
的元素,值为"abc"
(string) - 下标为
666
的元素,值为"666"
(string)
1 | --请补全代码 |
下标进阶
在上一节,我们学习了如何自定义下标,其实在Lua中,下标也可以是字符串,如下面的例子
1 | t = { |
可见,在使用string
作为下标时,table
的灵活性提升了一个数量级。
string
作为下标时,也可以动态赋值:
1 | t = {} -- 空table |
下面你需要完成:
- 新建
table
变量t
- 下标为
apple
的元素,值为123
(number) - 下标为
banana
的元素,值为"abc"
(string) - 下标为
1@1
的元素,值为"666"
(string)
1 | --请补全代码 |
table小测验
下面的代码,将会打印什么?
1 | t = { |
table小测验2
下面的代码,将会打印什么?
1 | t = { |
Lua全局变量与table
在前面我们知道了,在table
中,可以直接用table名[下标]
或table名.string下标
来访问元素
实际上,在Lua中,所有的全局变量全部被存放在了一个大table
中,这个table
名为:_G
我们可以用下面的例子来示范:
1 | n = 123--新建变量 |
现在,你明白为什么说万物基于table
了吧?
你需要完成下面的任务:
- 已知有一个全局变量,名为
@#$
- 请新建一个变量
result
- 将
@#$
变量里的值赋值给result
1 | _G["@#$"] = 123 |
table小测试3
请新建一个名为t的table,满足以下要求
t[10]
可获得number
类型数据100
t.num
可获得number
类型数据12
t.abc[3]
可获得string
类型数据abcd
t.a.b.c
可获得number
类型数据789
1 | --请补全代码 |
table.concat
table.concat (table [, sep [, i [, j ] ] ])
将元素是string
或者number
类型的table
,每个元素连接起来变成字符串并返回。
可选参数sep
,表示连接间隔符,默认为空。
i
和j
表示元素起始和结束的下标。
下面是例子:
1 | local a = {1, 3, 5, "hello" } |
请完成下面的任务:
- 已知table变量t,
- 将t中的结果全部连起来
- 间隔符使用
,
- 并使用print打印出来
1 | t = {"a","b","c","d"} |
table删减
table.insert (table, [pos ,] value)
在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。pos 的默认值是表的长度加一,即默认是插在表的最后。
table.remove (table [, pos])
在表 table 中删除索引为 pos(pos 只能是 number 型)的元素,并返回这个被删除的元素,它后面所有元素的索引值都会减一。pos 的默认值是表的长度,即默认是删除表的最后一个元素。
下面是例子:
1 | local a = {1, 8} --a[1] = 1,a[2] = 8 |
请完成下面的任务:
- 已知table变量
t
, - 去除t中的第一个元素
- 然后这时,在t的第三个元素前,加上一个number变量,值为810
1 | t = {1,2,3,4,5,6,7,8,9} |
循环
while循环
在实际功能实现中,经常会遇到需要循环运行的代码,比如从1到100填充table数据,我们可以直接用循环语句来实现
我们首先来学习while
这个循环语法,整体的格式如下:
1 | while 继续循环判断依据 do |
下面举一个例子,我们计算从1加到100的结果:
1 | local result = 0 |
上面的代码,就是当num≤100时,result不断地加num,并且num每次循环后自己加1
理解了上面的代码,我们来完成下面一个简单的任务吧:
- 已知两个number类型的变量
min
和max
- 请计算从
min
与max
之间,所有3的倍数
的和 - 打印出结果
1 | min,max = 114,514 --这个结果应为42009 |
for循环
for循环在某些程度上,和while循环很相似,但是for循环可以更加简洁地表达中间累积的量
我们首先来学习for
这个循环语法,整体的格式如下:
1 | for 临时变量名=开始值,结束值,步长 do |
其中,步长
可以省略,默认为1
临时变量名
可以直接在代码区域使用(但不可更改),每次循环会自动加步长值
,并且在到达结束值
后停止循环
下面举一个例子,我们计算从1加到100的结果:
1 | local result = 0 |
上面的代码,就是当i≤100时,result不断地加i,并且i每次循环后增加1
理解了上面的代码,我们来完成下面一个简单的任务吧:
- 已知两个number类型的变量
min
和max
- 请计算从
min
与max
之间,所有7的倍数的和 - 打印出结果
1 | min,max = 114,514 --这个结果应为17955 |
中断循环
前面我们学习了循环语句,有些时候循环运行到一半,我们不想再继续运行了,怎么办呢?
我们可以在一个循环体中使用break
,来立即结束本次循环,继续运行下面的代码
比如像下面这样,计算1-100相加途中,小于100
的最大的和
:
1 | result = 0 |
可以看见,当发现和大于100后,代码立即把result
的值还原到了加上当前数字之前的状态,并且调用break
语句,立即退出了本次循环
在while
中,我们也可以使用break
:
1 | result = 0 |
我们在这里直接使用了死循环(因为while
的继续运行判断依据始终为true
),整体逻辑也和之前for的代码一致,当发现和大于100后,代码立即把result
的值还原到了加上当前数字之前的状态,并且调用break
语句,立即退出了本次循环
现在你需要完成一项任务:
- 请求出小于变量max的13的倍数的最大值(max大于0)
- 并将结果打印出来
- 本题理论上不用循环就能实现,但是为了练习一下技巧,请用for循环来实现
1 | max = 810 --结果应为806 |
循环测试题1(自测题)
前面我们学习了循环语句,我们需要完成下面的任务
我们知道,print
函数可以打印一行完整的输出
那么,已知变量a,请打印出下面的结果:
(a为大于0的整数,且需要输出a行数据,数据从1开始,每行与上一行的差为2)
1 | 1 |
做题区域:
1 | a = 10 |
循环测试题2(自测题)
我们需要完成下面的任务
那么,已知变量a,请打印出下面的结果:
(a为大于0的整数,且需要输出a行数据,第一行为一个,后面每行多一个)
1 | * |
做题区域:
1 | a = 10 |
循环测试题3(自测题)
我们需要完成下面的任务
那么,已知变量a,请打印出下面的结果:
(a为大于0的整数,且需要输出a行数据,按图示规律输出)
1 | 1 |
做题区域:
1 | a = 20 |
循环测试题4(自测题)
- 有一只猴子,第一天摘了若干个桃子 ,当即吃了一半,但还觉得不过瘾 ,就又多吃了一个。
- 第2天早上又将剩下的桃子吃掉一半,还是觉得不过瘾,就又多吃了两个。
- 以后每天早上都吃了前一天剩下的一半加天数个(例如,第5天吃了前一天剩下的一半加5个)。
- 到第n天早上再想吃的时候,就只剩下一个桃子了。
- 那么,已知变量a为最后一天的天数,请打印出第一天的桃子数。
- 如:a为5时,输出114
做题区域:
1 | a = 6 |
详解string库
string.sub
接下来几节会讲解string库的各种接口
1 | string.sub(s, i [, j]) |
返回字符串 s
中,从索引 i
到索引 j
之间的子字符串。
i 可以为负数,表示倒数第几个字符。
当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。
当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。
下面是例子:
1 | print(string.sub("Hello Lua", 4, 7)) |
值得注意的是,我们可以使用冒号来简化语法,像下面这样:
1 | s = "12345" |
请完成下面的任务:
- 已知字符串变量
s
,请分别打印出(每种一行): s
从第4个字符开始,到最后
的值s
从第1个字符开始,到倒数第3个字符
的值s
从倒数第5个字符开始,到倒数第2个字符
的值
1 | s = "1919810" |
string.rep
string.rep(s, n)
返回字符串 s 的 n 次拷贝。
示例代码:
1 | print(string.rep("abc", 3)) |
请完成下面的任务:
打印一行数据,数据内容为810个114514
1 | --补全代码 |
string.len
string.len(s)
接收一个字符串,返回它的长度。
示例代码:
1 | s = "hello lua" |
请完成下面的任务:
- 新建一个变量s,使数据内容为810个114514
- 并打印出字符串s的长度
1 | s = --补全代码 |
大小写转换
string.lower(s)
接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。
string.upper(s)
接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。
示例代码:
1 | s = "hello lua" |
请完成下面的任务:
已知一个变量s
,打印出全是大写字母的s字符串
1 | s = "asd8938KJjsidiajdl;(()k)" |
string.format
string.format(formatstring, …)
按照格式化参数formatstring
,返回后面...
内容的格式化版本。
编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:
它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,及如何放入它们。
一个指示由字符%
加上一个字母组成,这些字母指定了如何格式化参数,例如d
用于十进制数、x
用于十六进制数、o
用于八进制数、f
用于浮点数、s
用于字符串等。
示例代码:
1 | print(string.format("%.4f", 3.1415926)) -- 保留4位小数 |
请完成下面的任务:
- 已知一个变量
n
,为number
类型整数 - 打印出
n:
连上n
值的字符串
1 | n = 810 |
string的本质
这一节我们来讲解字符串的本质
字符串
,是用来存储一串字符的,但是它的本质就是一串数字
。如何用一串数字来代表一串字符呢?
在计算机中,每一个符号都对应着一个数字,但是在讲解这个知识之前,我们了解一下补充知识:
1 | 在大多数编程语言中,我们使用0x开头来表示这个数字是16进制的。 |
接下来,你需要了解,每一个符号都对应着一个数字,比如:
0
对应着0x30
、1
对应着0x31
a
对应着0x61
、b
对应着0x62
A
对应着0x41
、B
对应着0x42
上面的编码规则,我们称之为ascii码,具体想了解可以打开下面的网址查看:
当然,1字节最大为0xff,即256,只能存下一部分符号,大部分的中文按某些编码,一个中文占用2或3个字节
计算机如何解析这些数据,我们不需要了解,当你知道了上面的知识后,你应该可以理解下面的描述:
1 | 字符串"apple"实际上的内容就是下面的一串数字: |
同时,lua的字符串中可以保存任何数值,即使是0x00这种不代表任何含义的数,也可以保存
1 | 补充:在其他语言中(如C),0x00代表字符串结束,但是在lua中并不是这样。 |
比如下面的描述:
1 | 有一串lua字符串中的数据为: |
下面你需要思考一个问题:一串字符串数据如下,它的实际内容是什么(指人能看见的字符串内容,如abcd)?
0x62,0x61,0x6e,0x61,0x6e,0x61
string.char
string.char (…)
接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。当参数为空时,默认是一个 0。
如果上一章节有认真学习过了的话,这段话应该是很好理解的。实质上就是把计算机认识的一串数字,变成字符串变量,并且字符串内的数据就是要存的那串数据。
示例代码:
1 | str1 = string.char(0x30,0x31,0x32,0x33) |
请完成下面的任务:
- 已知一个字符串的每个字符在数组t中按顺序排列
- 请根据t的值,打印出字符串内容(一行数据)
- 注:这个字符串存储的不一定是可见的字符
1 | t = {0x79,0x6F,0x75,0x20,0x61,0x72,0x65,0x20,0x72,0x69,0x67,0x68,0x74} |
string.byte
string.byte(s [, i [, j ] ])
返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。i 的默认值为 1,即第一个字节,j 的默认值为 i 。
这个函数功能刚好和前面的string.char相反,是提取字符串中实际的数值。
示例代码:
1 | str = "12345" |
请完成下面的任务:
- 已知字符串s
- 请把s中代表的数据,全部相加,并打印出来
1 | s = string.char(1,2,3,4,5,6,7,8,9) |
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
时,便不会使用正则功能。
示例代码:
1 | --只会匹配到第一个 |
请完成下面的任务:
- 已知字符串s,里面有很多相同的字符串
- 请找出字符串s中,所有字符串awsl的位置
- 使用print打印结果,结果一行一个
- 如字符串12awslawslaw,输出3和7
1 | s = "12awsaslwlaawsllslllswasllalssawwlawslaw" |
string.gsub
string.gsub(s, p, r [, n])
将目标字符串s
中所有的子串p
替换成字符串r。
可选参数n
,表示限制替换次数。
返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。
特别提示:这个函数的目标字符串s,也是支持正则的
下面是例子:
1 | print(string.gsub("Lua Lua Lua", "Lua", "hello")) |
同样的,我们也可以使用冒号来简化语法,像下面这样:
1 | s = "12345" |
请完成下面的任务:
- 已知字符串变量s,请分别打印出(每种一行):
- 把字符串s中,前5个a,替换为b
- 把字符串s中,前3个c,替换为xxx
- 把结果打印出来,一行数据
1 | s = "asdicagydausckfugdaflgscdabgsdbahhacbshbsd" |
正则匹配相关内容等待更新
跨文件调用
在编写代码时,随着逻辑逐渐复杂,我们的代码量也会变大。虽然有函数
可以把一部分代码逻辑封装起来,但是所有代码都放到一个文件里,显然也不是个好办法。
所以我们需要将一些代码放到不同文件中,通过文件来区分这些代码的功能。
比如我们有下面这个函数:
1 | ---函数功能: |
我们新建一个文件叫tools.lua
,把这个函数放进去,现在,整个文件如下面这样:
tools.lua
1 | ---函数功能: |
现在,我们封装的这个函数就能在其他文件里被调用了,具体代码如下:
1 | --引用tools.lua文件,并加载 |
当调用了require
接口后,Lua虚拟机会自动加载你调用的文件,执行文件的内容
,然后返回你文件里return的结果
。
为了更好地理解这段话,我们可以看下面两个文件,其中run.lua是被运行的那个入口文件
test.lua
1 | --以便一会儿返回使用的table |
run.lua
1 | local test = require("test") |
同时,每个文件最多只会被require
一次,如果有多个require
,只有第一次会执行
待添加
- table -> sort排序
- string -> 正则、gsub、find、match
- 迭代器 for xx in xx 、string.gmatch
- 元表
- coroutine
↓各种接口↓
- math库
- os库
- io库
- 位运算 (>> <<)
- 杂项接口
- debug库