注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
一. 局部变量和代码块
Lua 语言中的变量在默认情况下是全局变量,所有的局部变量在使用前必须声明。与全局变量不同,局部变量的生效范围仅限于声明它的代码块。
x = 10
local i = 1 -- 对于代码段来说是局部的
while i <= x do
local x = i * 2 -- 对于循环体来说是局部的
print(x) -- > 2, 4, 6, 8, 10 ...
i = i + 1
end
print(x) --> 10 (全局的)
好处: 尽可能地使用局部变量是一种良好地编程风格。
首先,局部变量可以避免由于不必要地的命名造成全局变量的混乱;其次,局部变量还能避免同一程序中不同代码部分中的命名冲突;再次,访问局部变量比访问全局变量更快;最后,局部变量会随着其作用域的结束而消失,从而使得垃圾收集器释放
Lua语言发行版中有一个用于全局变量检查的模块 strict.lua, 如果试图在一个函数中对不存在的全局变量赋值或者使用不存在的全局变量,将会抛出异常。
局部变量多重赋值,多余值会被丢弃,多余变量被赋值为nil。如果一个声明中没有赋初值,则变量会被初始化为nil:
local a, b = 1, 10
if a < b then
print(a) --> 1
local a -- '= nil'是隐式的
print(a) --> nil
end
print(a, b) -- > 1 10
全局变量赋值给局部变量的用法
-- Lua语言中有一种常见的用法
local foo = foo
这段代码声明了一个局部变量 foo ,然后用全局变量 foo 对其赋初值,这个用法在需要提高对foo的访问速度时很有用。尤其是在进行运行时动态替换(猴子补丁)时,即使其他代码把 print 动态替换成了其他函数,在local print = print 语句之前的所有代码使用的还都是原先的print 函数。
二. 控制结构(Lua语言将所有不是false 和 nil 的值当作真,特别地,Lua语言将0和空字符串也当作真)
2.1 if then else
if 语句先测试其条件,并根据条件是否满足执行对应的 then 部分 或 else部分。else 部分是可选的
if a < 0 then a = 0 end
if a < b then return a else return b end
if line > MAXLINES then
showpage()
line = 0
end
编写嵌套 if 语句,可以使用elseif(else后面紧跟一个if)
if op == "+" then
r = a + b
elseif op == "-" then
r = a - b
elseif op == "*" then
r = a *b
elseif op == "/" then
r = a / b
else
error("invalid operation")
end
由于Lua语言不支持 switch 语句,所以这种一连串的else-if 语句比较常见。
2.2 while
顾名思义,当条件为真时while 循环会重复执行其循环体。Lua语言先测试while语句的条件,若条件为假则循环结束,否则,Lua会执行循环体并不断重复这个过程
local i = 1
while a[i] do
print(a[i])
i = i + 1
end
2.3 repeat
顾名思义,repeat-until 语句会重复执行其循环体直到条件为真时结束。由于条件测试在循环体之后执行,所以循环体至少会执行一次.
-- 输出第一个非空的行
local line
repeat
line = io.read()
until line ~= ""
print(line)
2.4 数值型for
for 语句有两种形式:数值型(numberical)for 和 泛型(generic)for
2.4.1 数值型for的语法如下:
for var = exp1, exp2, exp3 do
something
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) then
print(i)
break
end
end
为了更好使用for循环,还需要了解一些细节:
1.在循环开始前,三个表达式都会运行一次;
2.控制变量是被for语句自动声明的局部变量,且其作用范围仅限于循环体内。
for i = 1, 10 do print(i) end
max = i -- 可能会出错,此处的‘i’是全局的
3. 不要改变控制变量的值,随意改变控制变量的值可能产生不可预知的结果。
2.4.2 泛型for(pairs、ipairs、io.lines等) 详情见:语言特性-迭代器和泛型for
使用恰当的迭代器可以在保证代码可读性的情况下遍历几乎所有的数据结构
2.5 break、return 和 goto
break 和 return 语句用于从当前的循环结构中跳出,goto语句则允许跳转到函数中的几乎任何地方。
break语句结束循环,该语句会中断包含它的内层循环(例如for、repeat或者while);该语句不能在循环体外使用。break中断后,程序会紧接着被中断的循环继续执行
return语句用于返回函数的执行结果或简单地结束函数地运行。所有函数的最后都有一个隐含的return
按照语法,return只能是代码块中的最后一句:换句话说,它只能是代码块中的最后一句,或者是end、else和until之前的最后一句
goto语句用于将当前程序跳转到相应的标签处继续执行。goto语句一直以来备受争议,很多人认为它们不利于程序开发并应该在编程语言中禁止,不过尽管如此,仍有很多语言出于很多原因保留了goto语句。goto语句有很强大的功能,只要足够细心,就能利用它提高代码质量。
在Lua语言中的用法是,保留字goto后面紧跟着标签名,标签名可以是任意有效的标识符。标签的语法为:标签名称前后各紧跟两个冒号,形如::name::
使用goto跳转时,Lua语言设置了一些限制。
1. 标签遵循常见的可见性规则,因此不能直接跳转到一个代码块中的标签(因为代码块中的标签对外不可见)
2. goto不能跳转到函数外
3. goto不能跳转到局部变量的作用域
goto 语句在其他编程语言中的典型且正确的使用方式(在Lua不存在的代码结构)。
例如 continue、多级break、多级continue、redo和局部错误处理等。
continue语句仅仅相当于一个跳转到位于循环体最后位置处标签的goto语句,redo语句则相当于跳转到代码块开始位置的goto语句
-- 使用goto 实现其他编程语言中的 continue 和 redo
while some_condition do
::redo::
if some_other_condition then goto continue
else if yet_another_condition then goto redo
end
some code
::continue::
end
Lua语言规范中有个很有用的细节,局部变量的作用域终止于声明变量的代码块中的最后一个有效语句处,标签被认为是无效语句
while some_condition do
if some_other_condition then goto continue end
local var = something
-- some code
::continue:: -- 此时contine标签位于代码块中最后一个有效语句,所以goto并未跳转进入变量var的作用域内
end
使用goto 编写状态机
::s1:: do
local c = io.read(1) -- 以字符串读取1个字符 '010101'
if c == '0' then goto s2
elseif c == nil then print'ok';return
else goto s1
end
end
::s2:: do
local c = io.read(1)
if c == '0' then goto s1
elseif c == nil then print'not ok';return
else goto s2
end
end
goto s1
需求:迷宫游戏,迷宫中有几个房间,每个房间的东南西北方向各有一扇门。玩家每次可以输入移动方向,如果在这个方向上有一扇门,则玩家可以进入对应的房间,否则程序输出一个警告,玩家的最终目的是从第一个房间走到最后一个房间(使用goto编写状态机完成)
goto room1 -- 起始房间
::room1:: do
local move = io.read()
if move == "south" then goto room3
elseif move == "east" then goto room2
else
print("invalid move")
goto room1 -- 待同一房间
end
end
::room2:: do
local move = io.read()
if move == "south" then goto room4
elseif move == "west" then goto room1
else
print("invalid move")
goto room2
end
end
::room3:: do
local move = io.read()
if move == "north" then goto room1
elseif move == "ease" then goto room4
else
print("invalid move")
goto room3
end
end
::room4:: do
print("Congratulations, you won!")
end