Lua 第8部分 补充知识
8.1 局部变量和代码块
Lua 语言中的变量在默认情况下是全局变量 ,所有的局部变量在使用前必须声明 。 与全局变量不同,局部变量的生效范围仅限于声明它的代码块。一个代码块( block )是一个控制结构的主体,或是一个函数的主体,或是一个代码段(即变量被声明时所在的文件或字符串):
x = 10
local i = 1 -- 对于代码段来说是局部的while i <= x dolocal x = i*2 -- 对于循环体来说是局部的print(x)i = i + 1
endif i > 20 then local x -- 对于then来说是局部的x = 20print(x + 2) -- 如果测试成功输出22
elseprint(x) --> 10 (全局的)
endprint(x) --> 10 (全局的)
请注意,上述示例在交互模式中不能正常运行。 因为在交互模式中,每一行代码就是一个代码段(除非不是一条完整的命令)。 一旦输入示例的第二行, Lua 语言解释器就会直接运行它并在下一行开始一个新的代码段。 这样,局部( local )的声明就超出了原来的作用范围 。 解决这个问题的一种方式是显式地声明整个代码块, 即将它放入一对 do-end 中 。一旦输入了do,命令就只会在遇到匹配的 end 时才结束,这样 Lua 语言解释器就不会单独执行每一行的命令。
当需要更好地控制某些局部变量的生效范围时, do 程序块也同样有用:
local x1, x2
dolocal a2 = 2*a local d = (b^2 - 4*a*c)^(1/2)x1 = (-b + d)/a2x2 = (-b - d)/a2
end -- 'a2'和'd'的范围在此结束
print(x1, x2) -- 'x1'和'x2'仍在范围内
尽可能地使用局部变量是一种良好的编程风格。 首先,局部变量可以避免由于不必要的命名而造成全局变量的混乱;其次,局部变量还能避免同一程序中不同代码部分中的命名冲突;再次,访问局部变量比访问全局变量更快;最后,局部变量会随着其作用域的结束而消失,从而使得垃圾收集器能够将其释放。
鉴于局部变量优于全局变量,有些人就认为 Lua 语言应该把变量默认视为局部的。 然而,把变量默认视为局部的也有一系列的问题(例如非局部变量的访问问题)。一个更好的解决办法并不是把变量默认视为局部变量,而是在使用变量前必须先声明 。 Lua 语言的发行版中有一个用于全局变量检查的模块 strict.lua , 如果试图在一个函数中对不存在的全局变量赋值或者使用不存在的全局变量 ,将会抛出异常。 这在开发 Lua 语言代码时是一个良好的习惯。
局部变量的声明可以包含初始值,其赋值规则与常见的多重赋值一样:多余的值被丢弃 ,多余的变量被赋值为 nil 。 如果一个声明中没有赋初值,则变量会被初始化为 nil:
local a, b = 1, 10
if a < b thenprint(a) --> 1local a -- '= nil'是隐式的print(a) --> nil
end
print(a, b) --> 1 10
Lua 语言中有一种常见的用法:
local foo = foo
这段代码声明了一个局部变量 foo 然后用全局变量 foo 对其赋初值(局部变量 foo 只有在声明之后才能被访问)。 这个用法在需要提高对 foo 的访问速度时很有用。 当其他函数改变了全局变量 foo 的值,而代码段又需要保留 foo 的原始值时,这个用法也很有用,尤其是在进行运行时动态替换时。 即使其他代码把 print 动态替换成了其他函数,在 local print =print 语句之前的所有代码使用的还都是原先的 print 函数。
有些人认为在代码块的中间位置声明变量是一个不好的习惯,实际上恰恰相反:我们很少会在不赋初值的情况下声明变量 ,在需要时才声明变量可以避免漏掉初始化这个变量。 此外,通过缩小变量的作用域还有助于提高代码的可读性。
8.2 控制结构
Lua 语言提供了一组精简且常用的控制结构,包括用于条件执行的 if 以及用于循环的 while 、 repeat 和 for 。 所有的控制结构语法上都有一个显式的终结符: end用于终结 if 、 for 及 while 结构, until 用于终结 repeat 结构 。
控制结构的条件表达式的结果可以是任何值。 请记住,Lua 语言将所有不是 false 和 nil 的值当作真(特别地, Lua 语言将 0 和空字符串也当作真)。
8.2.1 if then else
if 语句先测试其条件,并根据条件是否满足执行相应的 then 部分或 else 部分。 else 部分
是可选的 。
if a < 0 then a = 0 endif a < b then return a else return b endif line > MAXLINES thenshowpage()line = 0
end
如果要编写嵌套的 if 语句,可以使用 elseif 。 它类似于在 else 后面紧跟一个 if,但可以避免重复使用 end:
if op == "+" then r = a + b
elseif op == "-" then r = a - b
elseif op == "*" then r = a * b
elseif op == "/" then r = a / b
elseerror("invalid operation")
end
由于 Lua 语言不支持 switch 语句,所以这种一连串的 else-if 语句比较常见。
8.2.2 while
顾名思义,当条件为真时 while 循环会重复执行其循环体。 Lua 语言先测试 while 语句的条件,若条件为假则循环结束;否则, Lua 会执行循环体并不断地重复这个过程。
local i = 1
while a[i] do print(a[i])i = i + 1
end
8.2.3 repeat
顾名思义, repeat-until 语句会重复执行其循环体直到条件为真时结束。 由于条件测试在循环体之后执行,所以循环体至少会执行一次。
-- 输出第一个非空的行
local line
repeatline = io.read()
until line ~= ""
print(line)
和大多数其他编程语言不同,在 Lua 语言中,循环体内声明的局部变量的作用域包括测试条件:
-- 使用Newton-Raphson计算'x'的平方根
local sqr = x / 2
repeatsqr = (sqr + x/sqr) / 2local error = math.abs(sqr^2 - x)
until error < x/10000 -- 局部变量error此时依然可见
8.2.4 数值型 for
for 语句有两种形式: 数值型 for 和泛型 for 。
数值型 for 的语法如下:
for var = exp1, exp2, exp3 dosomething
end
在这种循环中, var 的值从 exp1 变化到 exp2 之前的每次循环会执行 something ,并在每次循环结束后将步长 exp3 增加到 var 上。 第三个表达式 exp3 是可选的,若不存在, Lua语言会默认步长值为 1 。 如果不想给循环设置上限,可以使用常量 math.huge:
for i = 1, math.huge do if (0.3*i^3 - 20*i^2 - 500 >= 0) thenprint(i)breakend
end
为了更好地使用 for 循环,还需要了解一些细节。首先,在循环开始前, 三个表达式都会运行一次;其次,控制变量是被 for 语句自动声明的局部变量 ,且其作用范围仅限于循环体内 。一种典型的错误是认为控制变量在循环结束后仍然存在:
for i = 1, 10 do print(i) end
max = i -- 可能会出错!此处的'i'是全局的
如果需要在循环结束后使用控制变量的值(通常在中断循环时),则必须将控制变量的值保存到另一个变量中:
-- 在一个列表中寻找一个值
local found = nil
for i = 1, #a doif a[i] < 0 thenfound = i -- 保存'i'的值breakend
end
print(found)
最后,不要改变控制变量的值,随意改变控制变量的值可能产生不可预知的结果。 如果要在循环正常结束前停止 for 循环,那么可以参考上面的例子,使用 break 语句。
8.2.5 泛型 for
泛型 for 遍历迭代函数返回的所有值,例如我们已经在很多示例中看到过的 pairs 、ipairs 和 io.lines 等。 虽然泛型 for 看似简单,但它的功能非常强大。 使用恰当的迭代器可以在保证代码可读性的情况下遍历几乎所有的数据结构。
当然,我们也可以自己编写迭代器。 尽管泛型 for 的使用很简单 , 但编写迭代函数却有不少细节需要注意。
与数值型 for 不同,泛型 for 可以使用多个变量 , 这些变量在每次循环时都会更新。 当第一个变量变为 nil 时 ,循环终止。 像数值型 for 一样,控制变量是循环体中的局部变量 ,我们也不应该在循环中改变其值。
8.3 break 、 return 和 goto
break 和 return 语句用于从当前的循环结构中跳出, goto 语句则允许跳转到函数中的几乎任何地方。
我们可以使用 break 语句结束循环,该语句会中断包含它的内层循环(例如 for 、repeat或者 while );该语句不能在循环外使用 。 break 中断后,程序会紧接着被中断的循环继续执行。
return 语句用于返回函数的执行结果或简单地结束函数的运行。 所有函数的最后都有一个隐含的 return , 因此我们不需要在每一个没有返还值的函数最后书写 return 语句 。
按照语法, return 只能是代码块中的最后一句:换句话说,它只能是代码块的最后一句,或者是 end 、 else 和 until 之前的最后一句。 例如,在下面的例子中, return 是 then 代码块的最后一句 :
local i = 1
while a[i] doif a[i] == v then return i endi = i + 1
end
通常,这些地方正是使用 return 的典型位置, return 之后的语句不会被执行。 不过,有时在代码块中间使用 return 也是很有用的 。 例如,在调试时我们可能不想让某个函数执行。 在这种情况下,可以显式地使用一个包含 return 的 do:
function foo()return --<< SYNTAX ERROR-- 'return'是下一个代码块的最后一句do return end -- OKother statements
end
goto 语句用于将当前程序跳转到相应的标签处继续执行。 goto 语句一直以来备受争议,至今仍有很多人认为它们不利于程序开发并且应该在编程语言中禁止。不过尽管如此,仍有很多语言出于很多原因保留了 goto 语句 。goto 语句有很强大的功能,只要足够细心,我们就能够利用它来提高代码质量。
在 Lua 语言中 , goto 语句的语法非常传统, 即保留字 goto 后面紧跟着标签名,标签名可以是任意有效的标识符。 标签的语法稍微有点复杂:标签名称前后各紧跟两个冒号,形如 ::name:: 。 这个复杂的语法是有意而为的,主要是为了在程序中醒目地突出这些标签 。
在使用 goto 跳转时·,Lua 语言设置了一些限制条件。 首先,标签遵循常见的可见性规则,因此不能直接跳转到一个代码块中的标签(因为代码块中的标签对外不可见 ) 。 其次,goto
不能跳转到函数外(注意第一条规则已经排除了跳转进一个函数的可能性)。最后,goto 不能跳转到局部变量的作用域。
关于 goto 语句典型且正确的使用方式,请参考其他一些编程语言中存在但 Lua 语言中不存在的代码结构,例如 continue 、多级 break 、多级 continue 、redo 和局部错误处理等。continue语句仅仅相当于一个跳转到位于循环体最后位置处标签的 goto 语句,而 redo 语句则相当于跳转到代码块开始位置的 goto 语句:
while some_condition do::redo:: if some_other_condition then goto continueelse if yet_another_condition then goto redoendsome code ::continue::
end
Lua 语言规范中一个很有用的细节是,局部变量的作用域终止于声明变量的代码块中的最后一个有效 ( non-void )语句处 ,标签被认为是无效( void ) 语句。 下列代码展示了这个实用的细节:
while some_condition doif some_other_condition then goto continue endlocal var = somethingsome code ::continue::
end
可能有人认为,这个 goto 语句跳转到了变量 var 的作用域内 。 但实际上这个 continue 标签出现在该代码块的最后一个有效语句后,因此 goto 并未跳转进入变量 var 的作用域内 。
goto 语句在编写状态机时也很有用 。 示例 8.1 给出了一个用于检验输入是否包含偶数个0的程序。
示例 8.1 一个使用 goto 语句的状态机的示例
::s1:: dolocal c = io.read(1)if c == '0' then goto s2elseif c == nil then print ('ok'); returnelse goto s1end
end::s2:: dolocal c = io.read(1)if c == '0' then goto s1elseif c == nil then print('not ok'); returnelse goto s2end
endgoto s1
虽然可以使用更好的方式来编写这段代码,但上例中的方法有助于将一个有限自动机自动地转化为 Lua 语言代码。
再举一个简单的迷宫游戏的例子。 迷宫中有几个房间,每个房间的东南西北方向各有一扇门。 玩家每次可以输入移动的方向,如果在这个方向上有一扇门 , 则玩家可以进入相应的房间,否则程序输出一个警告,玩家的最终目的是从第一个房间走到最后一个房间 。
这个游戏是一个典型的状态机,当前玩家所在房间就是一个状态。 为实现这个迷宫游戏,我们可以为每个房间对应的逻辑编写一段代码,然后用 goto 语句表示从一个房间移动到另一个房间。示例 8.2展示了如何编写一个由 4 个房间组成的小迷宫。
示例 8.2 一个迷宫游戏
goto room1 -- 起始房间::room1:: dolocal move = io.read()if move == "south" then goto room3elseif move == "east" then goto room2elseprint("invalid move")goto room1 -- 待在同一个房间end
end::room2:: dolocal move = io.read()if move == "south" then goto room4elseif move == "west" then goto room1elseprint("invalid move")goto room2 -- 待在同一个房间end
end::room3:: dolocal move = io.read()if move == "north" then goto room1elseif move == "east" then goto room4elseprint("invalid move")goto room3 -- 待在同一个房间end
end::room4:: doprint("Congratulations, you win!")
end
对于这个简单的游戏,使用数据驱动编程(使用表来描述房间和移动)是一种更好的设计方法。 不过,如果游戏中的每间房都各自不同,那么就非常适合使用这种状态机的实现方法。
相关文章:
Lua 第8部分 补充知识
8.1 局部变量和代码块 Lua 语言中的变量在默认情况下是全局变量 ,所有的局部变量在使用前必须声明 。 与全局变量不同,局部变量的生效范围仅限于声明它的代码块。一个代码块( block )是一个控制结构的主体,或是一个函…...
正则表达式三剑客之——awk命令
目录 一.什么是awk 二.awk的语法格式 1.选项 2. 模式(Pattern) 3. 操作(Action) 4. 输入文件(file) 5.总结 三.awk的工作原理 1. 逐行扫描输入 2. 匹配模式 1.正则表达式: 2.逻辑…...

BeeWorks Meet:私有化部署视频会议的高效选择
在数字化时代,视频会议已成为企业沟通协作的重要工具。然而,对于金融、政务、医疗等对数据安全和隐私保护要求极高的行业来说,传统的公有云视频会议解决方案往往难以满足其严格的安全标准。此时,BeeWorks Meet 私有化部署视频会议…...
[Mybatis-plus]
简介 MyBatis-Plus (简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变。Mybatis-plus官网地址 注意,在引入了mybatis-plus之后,不要再额外引入mybatis和mybatis-spring,避免因为版本…...

IPv6 技术细节 | 源 IP 地址选择 / Anycast / 地址自动配置 / 地址聚类分配
注:本文为 “IPv6 技术细节” 相关文章合集。 部分文章中提到的其他文章,一并引入。 略作重排,未整理去重。 如有内容异常,请看原文。 闲谈 IPv6 - 典型特征的一些技术细节 iteye_21199 于 2012-11-10 20:54:00 发布 0. 巨大的…...
【高频考点精讲】ES6 String的新增方法,处理字符串更方便了
ES6 String的新增方法:处理字符串从未如此优雅 【初级】前端开发工程师面试100题(一) 【初级】前端开发工程师面试100题(二) 【初级】前端开发工程师的面试100题(速记版) 作为天天和字符串打交道的码农,谁还没被indexOf和substring折磨过?ES6给String对象新增的几个方…...

【工具】使用 MCP Inspector 调试服务的完全指南
Model Context Protocol (MCP) Inspector 是一个交互式开发工具,专为测试和调试 MCP 服务器而设计。本文将详细介绍如何使用 Inspector 工具有效地调试和测试 MCP 服务。 1. MCP Inspector 简介 MCP Inspector 提供了直观的界面,让开发者能够ÿ…...

【音视频】AVIO输入模式
内存IO模式 AVIOContext *avio_alloc_context( unsigned char *buffer, int buffer_size, int write_flag, void *opaque, int (*read_packet)(void *opaque, uint8_t *buf, int buf_size), int (*write_packet)(void *opaque, uint8_t *buf, int buf_size), int64_t (*seek)(…...
AI与思维模型【76】——SWOT思维模型
一、定义 SWOT思维模型是一种用于分析事物内部和外部因素的战略规划工具。其中,S代表优势(Strengths),是指事物自身所具备的独特能力、资源或特点,这些因素有助于其在竞争中取得优势;W代表劣势(…...
大模型提示词如何编写
一、提示词的核心三要素 明确目标(What) 告诉 AI「你要它做什么」,越具体越好。 ❌ 模糊:写一篇文章 ✅ 清晰:写一篇 800 字的高考作文,主题 “坚持与创新”,结构分引言、三个论点(…...
python如何取消word中的缩进
在python-docx中,取消缩进可以通过将相应的缩进属性设置为None或0来实现。以下是取消不同类型缩进的方法: 取消左缩进 from docx import Documentdoc Document(existing_document.docx)for paragraph in doc.paragraphs:# 取消左缩进paragraph.paragr…...
DDL小练习
1.创建一张t_user表 要求属性有id(INT),name(VARCHAR),sex(VARCHAR),birthday(DATE) 其中id和name不能为空,添加数据并测试。 创建数据库 create database spt2503; 创建数据库中的t_user表 create table t_user (id int not null, name varchar(20) not…...

Uniapp:scroll-view(区域滑动视图)
目录 一、基本概述二、属性说明三、基本使用3.1 纵向滚动3.2 横向滚动一、基本概述 scroll-view,可滚动视图区域。用于区域滚动。 二、属性说明 属性名类型默认值说明平台差异说明scroll-xBooleanfalse允许横向滚动scroll-yBooleanfalse允许纵向滚动三、基本使用 3.1 纵向滚…...
【前端】【面试】在前端开发中,如何实现图片的渐进式加载,以及这样做的好处是什么?
题目:在前端开发中,如何实现图片的渐进式加载,以及这样做的好处是什么? 在浏览器端实现图片的“渐进式加载”(Progressive Image Loading)常用的三种方式 方法思路典型实现要点适用场景优缺点简述1. 使…...

单精度浮点运算/定点运算下 MATLAB (VS) VIVADO
VIVADO中单精度浮点数IP核计算结果与MATLAB单精度浮点数计算结果的对比 MATLAB定点运算仿真,对比VIVADO计算的结果 目录 前言 一、VIVADO与MATLAB单精度浮点数运算结果对比 二、MATLAB定点运算仿真 总结 前言 本文介绍了怎么在MATLAB中使用单精度浮点数进行运算…...
基于大模型对先天性巨结肠全流程预测及医疗方案研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 二、大模型在先天性巨结肠预测中的理论基础 2.1 大模型概述 2.2 大模型预测先天性巨结肠的可行性分析 三、术前预测与准备方案 3.1 大模型对术前病情的预测 3.1.1 疾病确诊预测 3.1.2 病情严重程度评估 3.2 …...

【AI插件开发】Notepad++ AI插件开发1.0发布和使用说明
一、产品简介 AiCoder是一款为Notepad设计的轻量级AI辅助插件,提供以下核心功能: 嵌入式提问:对选中的文本内容进行AI分析,通过侧边栏聊天界面与AI交互,实现多轮对话、问题解答或代码生成。对话式提问:独…...

【MySQL数据库入门到精通-07 函数-字符串函数、数值函数、日期函数和流程函数】
文章目录 一、字符串函数1. MySQL中的函数主要分为以下四类: 字符串函数、数值函数、日期函数、流程函数。下面是字符串函数常见的函数,见下表。2.具体代码实现3.结果 二、数值函数1.知识点2.具体代码实现3.结果 三、日期函数1.知识点2.具体代码实现3.结…...

Python图像处理——基于Retinex算法的低光照图像增强系统
1.项目内容 (1)算法介绍 ①MSRCR (Multi-Scale Retinex with Color Restoration) MSRCR 是多尺度 Retinex 算法(MSR)的扩展版,引入了色彩恢复机制以进一步提升图像增强质量。MSR 能有效地压缩图像动态范围ÿ…...

如何在JDK17项目中改成1.8
1.调整 Spring Boot 版本 由于 Spring Boot 3.x 最低要求 JDK 17,所以如果要使用 JDK 8,需要把 spring-boot-starter-parent 的版本降低到 2.7.x 系列,这个系列是支持 JDK 8 的。示例如下: <parent><groupId>org.sp…...

【不同名字的yolo的yaml文件名是什么意思】
以下是这些 YOLO 系列配置文件的详细解析,按版本和功能分类说明: 一、YOLOv3 系列 文件名核心特性适用场景yolov3.yaml原始 YOLOv3 结构,3 尺度预测(13x13,26x26,52x52)通用目标检测yolov3-spp.yaml增加 SPPÿ…...

Zephyr kernel Build System (CMake)介绍
目录 概述 1. 结构介绍 2 构建和配置阶段 2.1 配置阶段 2.2 Cmake编译 3 Zephy项目目录结构 3.1 文件架构 3.2 文件content 概述 本文主要介绍Zephyr kernel Build System CMake的功能,以及使用该工具构建项目,并详细介绍了每个目录以及目录下文…...

相对论大师-记录型正负性质BFS/图论-链表/数据结构
看到这一题我的第一个思路就是双向bfs 起点是a,终点还是a,但是flag是相反的(“越”的方向) tip1.可以用字典vis来存储flag 刚开始初始化时vissta,visend一个对应0、1 要求两个队列相…...
研发内控新规下的合规之道:维拉工时助力企业穿越IPO审查雷区
📌 背景 | 全面注册制下,研发内控成“必修课” 在全面注册制背景下,证监会发布的《监管规则适用指引——发行类第9号:研发人员及研发投入》(简称“发行类9号”),对企业的研发费用归集、研发工时…...

Jenkins流水线管理工具
文章目录 前言: DevOps时代的自动化核心 —Jenkins一、Jenkins是什么?二、Linux安装Jenkinswar包方式安装依赖环境下载 Jenkins WAR 包启动 Jenkins 服务启动日志验证配置插件镜像源 docker镜像方式安装依赖环境拉取 Jenkins 镜像运行 Jenkins 容器获取初…...

嵌入式开发:基础知识介绍
一、嵌入式系统 1、介绍 以提高对象体系智能性、控制力和人机交互能力为目的,通过相互作用和内在指标评价的,嵌入到对象体系中的专用计算机系统。 2、分类 按其形态的差异,一般可将嵌入式系统分为:芯片级(MCU、SoC&am…...

el-table中el-input的autofocus无法自动聚焦的解决方案
需求 有一个表格展示了一些进度信息,进度信息可以修改,需要点击进度信息旁边的编辑按钮时,把进度变为输入框且自动聚焦,当鼠标失去焦点时自动请求更新接口。 注:本例以vue2 element UI为例 分析 这个需求看着挺简单…...

一文了解智慧教育顶刊TLT的研究热点
本文聚焦于IEEE Transactions on Learning Technologies(TLT)期刊,通过图文结合的方式,梳理了2025年第18卷的研究热点,帮助读者把握教育技术与人工智能交叉领域的研究进展,深入了解智能学习系统、自适应学习…...

统计术语学习
基期、现期 作为对比参照的时期称为基期,而相对于基期的称为现期。 描述具体数值时我们称之为基期量和现期量。 【例 1】2017 年比 2016 年第三产业 GDP 增长 6.8%, (2016)为基期,(2017) 为现…...
NEGATIVE LABEL GUIDED OOD DETECTION WITH PRETRAINED VISION-LANGUAGE MODELS
1. 介绍: 这篇论文也是基于CLIP通过后处理的方法实现的OOD的检测,但是设计点在于,之前的方法是使用的ID的类别,这篇工作是通过添加一些在语义上非常不同于ID的类别的外分布类来做的OOD检测。 CLIP做OOD检测的这个系列里面我看的以及记录的第一篇就是MCM的方法,这也是确实是…...