Openresty实战应用(1)

快速上手Lua

  • Lua是 OpenResty 中使用的编程语言,掌握它的基本语法还是很有必要的。
  • Lua 是一个小巧精妙的脚本语言,诞生于巴西的大学实验室,这个名字在葡萄牙语里的含义是“美丽的月亮”。从作者所在的国家来看,NGINX 诞生于俄罗斯,Lua 诞生于巴西,OpenResty 诞生于中国,这三门同样精巧的开源技术都出自金砖国家,而不是欧美,也是挺有趣的一件事。
  • 回到 Lua 语言上。事实上,Lua 在设计之初,就把自己定位为一个简单、轻量、可嵌入的胶水语言,没有走大而全的路线。虽然你平常工作中可能没有直接编写 Lua 代码,但 Lua 的使用其实非常广泛。很多的网游,比如魔兽世界,都会采用 Lua 来编写插件;而键值数据库 Redis 则是内置了 Lua 来控制逻辑。
  • 另一方面,虽然 Lua 自身的库比较简单,但它可以方便地调用 C 库,大量成熟的 C 代码都可以为其所用。比如在 OpenResty 中,很多时候都需要你调用 NGINX 和 OpenSSL 的 C 函数,而这都得益于 Lua 和 LuaJIT 这种方便调用 C 库的能力。

Lua是什么?

  • 1993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没有糟蹋这个优美的单词,Lua 语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。
  • Lua 从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用 ANSIC 编写,能以 C 程序库的形式嵌入到宿主程序中。正由于上述特点,所以 Lua 在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏,比如 Escape from Monkey Island、World of Warcraft、大话西游,都采用了 Lua 来配合引擎完成数据描述、配置管理和逻辑控制等任务。即使像 Redis 这样中性的内存键值数据库也提供了内嵌用户 Lua 脚本的官方支持。

Lua 和 LuaJIT的区别

  • Lua 非常高效,它运行得比许多其它脚本(如 Perl、Python、Ruby)都快,这点在第三方的独立测评中得到了证实。
  • 尽管如此,仍然会有人不满足,他们总觉得“嗯,还不够快!”。LuaJIT 就是一个为了再榨出一些速度的尝试,它利用即时编译(Just-in Time)技术把 Lua 代码编译成本地机器码后交由 CPU 直接执行。
  • LuaJIT 2 的测评报告表明,在数值运算、循环与函数调用、协程切换、字符串操作等许多方面它的加速效果都很显著。凭借着 FFI 特性,LuaJIT 2 在那些需要频繁地调用外部 C/C++ 代码的场景,也要比标准 Lua 解释器快很多。目前 LuaJIT 2 已经支持包括 i386、x86_64、ARM、PowerPC 以及 MIPS 等多种不同的体系结构。
  • LuaJIT 是采用 C 和汇编语言编写的 Lua 解释器与即时编译器。LuaJIT 被设计成全兼容标准的 Lua 5.1 语言,同时可选地支持 Lua 5.2 和 Lua 5.3 中的一些不破坏向后兼容性的有用特性。因此,标准 Lua 语言的代码可以不加修改地运行在 LuaJIT 之上。LuaJIT 和标准 Lua 解释器的一大区别是,LuaJIT 的执行速度,即使是其汇编编写的 Lua 解释器,也要比标准 Lua 5.1 解释器快很多,可以说是一个高效的 Lua 实现。另一个区别是,LuaJIT 支持比标准 Lua 5.1 语言更多的基本原语和特性,因此功能上也要更加强大。

编译器选择

  • IDEA 下载插件 emmylua:install 安装并重启 idea。

在这里插入图片描述

  • 详细帮助文档参考此地址(官方文档):https://emmylua.github.io/zh_CN/
  • github地址:https://github.com/EmmyLua/IntelliJ-EmmyLua
  • 功能简要说明
    • 语法高亮
    • 全局/local/参数 等类型高亮
    • upvalue 高亮
    • 查找引用(Find usages)
    • 重命名(Rename(Shift+F6))
    • 跳转到定义(Go to definition(Ctrl + Mouse))
    • 快速跳转到文件(Navigate to file (Ctrl + Shift + N))
    • 快速跳转到符号(Navigate to symbol(Ctrl + Alt + Shift + N))
    • 快速跳转到类(Navigate to class(Ctrl + N))
    • 格式化
    • 自定义高亮颜色(Color settings page)
    • 代码完成提示
    • 基于注解的代码完成提示(–@class —@type 等等)
    • 大纲/快速大纲(Structure view)
    • 注释/反注释(Comment in/out)
    • 本地附加调试(目前只支持Windows32/64位程序)
    • 远程调试(基于mobdebug.lua,适用所有平台)
    • 代码模板
    • Postfix completion templates
    • Code intentions
    • Code inspections
    • Region folding
    • 文档(Quick Documentation(Ctrl + Q))
    • 支持lua5.3语法

Lua环境

  • Mac 安装 lua 环境:
curl -R -O http://www.lua.org/ftp/lua-5.2.3.tar.gz
tar zxf lua-5.2.3.tar.gz  
cd lua-5.2.3  
make macosx  
make test  
sudo make install 
# 安装成功
MacBook-Pro:lua-5.2.3 yangwei$ lua
Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
  • Hello World
> print("Hello World")
Hello World
  • Mac 安装 luajit 环境:
curl -R -O http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar zxf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5 
sudo make & make install
# 安装成功
MacBook-Pro:LuaJIT-2.0.5 yangwei$ luajit 
LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/
JIT: ON CMOV SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse

Lua基本数据类型

  • 函数 type 能够返回一个值或一个变量所属的类型。
> print(type("hello world"))
string
> print(type(print))
function
> print(type(true))
boolean
> print(type(360.0))
number
> print(type(nil))
nil

nil(空)

  • nil 是一种类型,Lua 将 nil 用于表示“无效值”。一个变量在第一次赋值前的默认值是 nil,将 nil 赋予给一个全局变量就等同于删除它。
> local num
> print(num)
nil
> num = 100
> print(num)
100
  • 值得一提的是,OpenResty 的 Lua 接口还提供了一种特殊的空值,即 ngx.null,用来表示不同于 nil 的“空值”。
  • 大家在使用 Lua 的时候,一定会遇到不少和 nil 有关的坑吧。有时候不小心引用了一个没有赋值的变量,这时它的值默认为 nil。如果对一个 nil 进行索引的话,会导致异常。
> local person = {name = "Tom", sex = "M"}
-- do something
> person = nil
-- do something
> print(person.name)
stdin:1: attempt to index global 'person' (a nil value)
stack traceback:
	stdin:1: in main chunk
	[C]: ?
  • 然而,在实际的工程代码中,我们很难这么轻易地发现我们引用了 nil 变量。因此,在很多情况下我们在访问一些 table 型变量时,需要先判断该变量是否为 nil,例如将上面的代码改成:
> local person = {name = "Tom", sex = "M"}
-- do something
> person = nil
-- do something
> if person ~= nil and person.name ~= nil then
>> print(person.name)
>> else
>> end
> 
  • 对于简单类型的变量,我们可以用 if (var == nil) then 这样的简单句子来判断。但是对于 table 型的 Lua 对象,就不能这么简单判断它是否为空了。一个 table 型变量的值可能是 {},这时它不等于 nil。我们来看下面这段代码:
local next = next
local a = {}
local b = { name = "Bob", sex = "Male" }
local c = { "Male", "Female" }
local d = nil

print(#a)
print(#b)
print(#c)
--print(#d)    -- error

if a == nil then
    print("a == nil")
end

if b == nil then
    print("b == nil")
end

if c == nil then
    print("c == nil")
end

if d == nil then
    print("d == nil")
end

if next(a) == nil then
    print("next(a) == nil")
end

if next(b) == nil then
    print("next(b) == nil")
end

if next(c) == nil then
    print("next(c) == nil")
end
  • 运行结果如下:
0
0
2
d == nil
next(a) == nil
  • 因此,我们要判断一个 table 是否为 {},不能采用 #table == 0 的方式来判断。可以用下面这样的方法来判断:
function isTableEmpty(t)
    return t == nil or next(t) == nil
end
  • 注意:next 指令是不能被 LuaJIT 的 JIT 编译优化,并且 LuaJIT 貌似没有明确计划支持这个指令优化,在不是必须的情况下,尽量少用。

boolean(布尔)

  • 布尔类型,可选值 true/false;Lua 中 nil 和 false 为“假”,其它所有值均为“真”。比如 0 和空字符串就是“真”;C 或者 Perl 程序员或许会对此感到惊讶。
local a = true
local b = 0
local c = nil

if a then
    print("a")
else
    print("not a")
end
if b then
    print("b")
else
    print("not b")
end
if c then
    print("c")
else
    print("not c")
end
  • 执行结果:
a
b
not c

number(数字)

  • number 类型用于表示实数,和 C/C++ 里面的 double 类型很类似。可以使用数学函数 math.floor(向下取整)和 math.ceil(向上取整)进行取整操作。
local order = 3.99
local score = 98.01

print(math.floor(order))
print(math.ceil(score))
  • 执行结果:
3
99
  • 一般地,Lua 的 number 类型就是用双精度浮点数来实现的。值得一提的是,LuaJIT 支持所谓的“dual-number”(双数)模式,即 LuaJIT 会根据上下文用整型来存储整数,而用双精度浮点数来存放浮点数。
  • 另外,LuaJIT 还支持“长长整型”的大整数(在 x86_64 体系结构上则是 64 位整数)。例如:
MacBook-Pro:LuaJIT-2.0.5 yangwei$ luajit 
> print(9223372036854775807LL - 1)
9223372036854775806LL

string(字符串)

  • Lua 中有三种方式表示字符串:
    • 使用一对匹配的单引号,例:‘hello’。
    • 使用一对匹配的双引号,例:“abclua”。
    • 字符串还可以用一种长括号(即[[ ]])括起来的方式定义。我们把两个正的方括号(即[[)间插入 n 个等号定义为第 n 级正长括号。就是说,0 级正的长括号写作 [[ ,一级正的长括号写作 [=[,如此等等。反的长括号也作类似定义;举个例子,4 级反的长括号写作 ]====]。一个长字符串可以由任何一级的正的长括号开始,而由第一个碰到的同级反的长括号结束。整个词法分析过程将不受分行限制,不处理任何转义符,并且忽略掉任何不同级别的长括号。这种方式描述的字符串可以包含任何东西,当然本级别的反长括号除外。例:[[abc\nbc]],里面的 “\n” 不会被转义。
  • 另外,Lua 的字符串是不可改变的值,不能像在 c 语言中那样直接修改字符串的某个字符,而是根据修改要求来创建一个新的字符串。
  • Lua 也不能通过下标来访问字符串的某个字符。想了解更多关于字符串的操作,请查看String 库章节。
local str1 = 'hello world'
local str2 = "hello lua"
local str3 = [["add\name",'hello']]
local str4 = [=[string have a [[]].]=]

print(str1)    -->output:hello world
print(str2)    -->output:hello lua
print(str3)    -->output:"add\name",'hello'
print(str4)    -->output:string have a [[]].
  • 在 Lua 实现中,Lua 字符串一般都会经历一个“内化”(intern)的过程,即两个完全一样的 Lua 字符串在 Lua 虚拟机中只会存储一份。每一个 Lua 字符串在创建时都会插入到 Lua 虚拟机内部的一个全局的哈希表中。 这意味着:
    • 创建相同的 Lua 字符串并不会引入新的动态内存分配操作,所以相对便宜(但仍有全局哈希表查询的开销);
    • 内容相同的 Lua 字符串不会占用多份存储空间;
    • 已经创建好的 Lua 字符串之间进行相等性比较时是 O(1) 时间度的开销,而不是通常见到的 O(n)

table(表)

  • table 类型实现了一种抽象的“关联数组”。“关联数组”是一种具有特殊索引方式的数组,索引通常是字符串(string)或者 number 类型,但也可以是除 nil 以外的任意类型的值。
local corp = {
    web = "www.google.com",   --索引为字符串,key = "web",
    --            value = "www.google.com"
    telephone = "12345678",   --索引为字符串
    staff = {"Jack", "Scott", "Gary"}, --索引为字符串,值也是一个表
    100876,              --相当于 [1] = 100876,此时索引为数字
    --      key = 1, value = 100876
    100191,              --相当于 [2] = 100191,此时索引为数字
    [10] = 360,          --直接把数字索引给出
    ["city"] = "Beijing" --索引为字符串
}

print(corp.web)               -->output:www.google.com
print(corp["telephone"])      -->output:12345678
print(corp[2])                -->output:100191
print(corp["city"])           -->output:"Beijing"
print(corp.staff[1])          -->output:Jack
print(corp[10])               -->output:360
  • 在内部实现上,table 通常实现为一个哈希表、一个数组、或者两者的混合。具体的实现为何种形式,动态依赖于具体的 table 的键分布特点。
  • 想了解更多关于 table 的操作,请查看 Table 库 章节。

lua正则

  • lua中正则表达式语法是最大的区别,Lua 使用 % 来进行转义,而其他语言的正则表达式使用 / 符号来进行转义。其次,Lua 中并不使用 ? 来表示非贪婪匹配,而是定义了不同的字符来表示是否是贪婪匹配。定义如下:
符号匹配次数匹配模式
+匹配前一字符 1 次或多次非贪婪
*匹配前一字符 0 次或多次贪婪
-匹配前一字符 0 次或多次非贪婪
?匹配前一字符 0 次或1次仅用于此,不用于标识是否贪婪
符号匹配模式
.任意字符
%a字母
%c控制字符
%d数字
%l小写字母
%p标点字符
%s空白符
%u大写字母
%w字母和数字
%x十六进制数字
%z代表 0 的字符
  • string.find的基本应用是在目标串内搜索匹配指定的模式的串。函数如果找到匹配的串,就返回它的开始索引和结束索引,否则返回nil。find 函数第三个参数是可选的,表示目标串中搜索的起始位置,例如当我们想实现一个迭代器时,可以传进上一次调用时的结束索引,如果返回了一个 nil 值的话,说明查找结束了。
local s = "hello world"
local i, j = string.find(s, "hello")
print(i, j) --> 1 5
  • string.match 可以使用返回迭代器的方式。
local s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
    print(w)
end

-- output :
--    hello
--    world
--    from
--    Lua

虚变量

  • 当一个方法返回多个值时,有些返回值有时候用不到,要是声明很多变量来一一接收,显然不太合适(不是不能)。Lua 提供了一个虚变量(dummy variable)的概念, 按照惯例以一个下划线(“_”)来命名,用它来表示丢弃不需要的数值,仅仅起到占位的作用。
  • 看一段示例代码:
-- string.find (s,p) 从string 变量s的开头向后匹配 string
-- p,若匹配不成功,返回nil,若匹配成功,返回第一次匹配成功
-- 的起止下标。

--start 值为起始下标,finish值为结束下标
local start, finish = string.find("hello", "he")
print(start, finish)                          --输出 1   2

local start = string.find("hello", "he")      -- start值为起始下标
print(start)                               -- 输出 1

-- 采用虚变量(即下划线),接收起始下标值,然后丢弃,finish接收结束下标值
local _, finish = string.find("hello", "he")
print(finish)                              --输出 2
print(_)                                   --输出 1, `_` 只是一个普通变量,我们习惯上不会读取它的值
  • 虚变量不仅仅可以被用在返回值,还可以用在迭代等。
-- 在for循环中的使用
print("all data:")
for i, v in ipairs(t) do
    print(i, v)
end

print("")
print("part data:")
for _,v in ipairs(t) do
    print(v)
end
  • 执行结果:
all data:
1       1
2       3
3       5

part data:
1
3
5
  • 当有多个返回值需要忽略时,可以重复使用同一个虚变量:
-- 多个占位
function foo()
    return 1, 2, 3, 4
end

local _, _, bar = foo();    -- 我们只需要第三个
print(bar)   -- output: 3

点号和冒号操作符的区别

  • 看下面示例代码:
local str = "abcde"
print("case1:", str:sub(1,2))
print("case1:", str.sub(str,1,2))
  • 执行结果:
case1:  ab
case2:  ab
  • 冒号操作会带入一个 self 参数,用来代表 自己。而点号操作,只是 内容 的展开。
  • 在函数定义时,使用冒号将默认接收一个 self 参数,而使用点号则需要显式传入 self 参数。
obj = { x = 20 }
function obj:fun1()
    print(self.x)
end
-- 等价于
obj = {x = 20}
function obj.fun1(self)
    print(self.x)
end

function(函数)

  • 在 Lua 中,函数 也是一种数据类型,函数可以存储在变量中,可以通过参数传递给其他函数,还可以作为其他函数的返回值。
local function foo()
    print("in the function")
    -- do something
    local x = 10
    local y = 20
    return x + y;
end
-- 把函数赋给变量
local a = foo

print(a())
--output:
in the function
30
  • 有名函数的定义本质上是匿名函数对变量的赋值。为说明这一点,考虑
function foo()
end
-- 等价于
foo = function() end
local function foo()
end
-- 等价于
local foo = function()
end
  • Lua 里面的函数必须放在调用的代码之前,下面的代码是一个常见的错误
local i = 100
i = add_one(i)

function add_one(i)
    return i + 1;
end
  • 我们将得到如下错误:
lua: 10-error.lua:2: attempt to call global 'add_one' (a nil value)
stack traceback:
        10-error.lua:2: in main chunk
        [C]: in ?
  • 为什么放在调用后面就找不到呢?原因是 Lua 里的 function 定义本质上是变量赋值,因此在函数定义之前使用函数相当于在变量赋值之前使用变量,Lua 世界对于没有赋值的变量,默认都是 nil,所以这里也就产生了一个 nil 的错误。

lua表达式

算术运算符

  • Lua 的算术运算符如下表所示:
算术运算符说明
+加法
-减法
*乘法
/除法
^指数
%取模
  • 示例代码:
print(1 + 2)       -->打印 3
print(5 / 10)      -->打印 0.5。 这是Lua不同于c语言的
print(5.0 / 10)    -->打印 0.5。 浮点数相除的结果是浮点数
-- print(10 / 0)   -->注意除数不能为0,计算的结果会出错
print(2 ^ 10)      -->打印 1024。 求2的10次方

local num = 1357
print(num % 2)       -->打印 1
print((num % 2) == 1) -->打印 true。 判断num是否为奇数
print((num % 5) == 0)  -->打印 false。判断num是否能被5整数

关系运算符

  • Lua 的关系运算符如下表所示:
关系运算符说明
<小于
>大于
<=小于等于
>=大于等于
==等于
~=不等于
  • 示例代码:
print(1 < 2)    -->打印 true
print(1 == 2)   -->打印 false
print(1 ~= 2)   -->打印 true
local a, b = true, false
print(a == b)  -->打印 false
  • 注意:Lua 语言中“不等于”运算符的写法为:~=
  • 在使用“==”做等于判断时,要注意对于 table, userdate 和函数, Lua 是作引用比较的。也就是说,只有当两个变量引用同一个对象时,才认为它们相等。可以看下面的例子:
local a = { x = 1, y = 0}
local b = { x = 1, y = 0}
if a == b then
    print("a==b")
else
    print("a~=b")
end
-- 输出
a~=b
  • 由于 Lua 字符串总是会被“内化”,即相同内容的字符串只会被保存一份,因此 Lua 字符串之间的相等性比较可以简化为其内部存储地址的比较。这意味着 Lua 字符串的相等性比较总是为 O(1)。
  • 而在其他编程语言中,字符串的相等性比较则通常为 O(n),即需要逐个字节(或按若干个连续字节)进行比较。

逻辑运算符

  • Lua 的逻辑运算符如下表所示:
逻辑运算符说明
and逻辑与
or逻辑或
not逻辑非
  • Lua 中的 and 和 or 是不同于 c 语言的。在 c 语言中,and 和 or 只得到两个值 1 和 0,其中 1 表示真,0 表示假。而 Lua 中 and 的执行过程是这样的:
    • a and b 如果 a 为 nil,则返回 a,否则返回 b;
    • a or b 如果 a 为 nil,则返回 b,否则返回 a。
  • 示例代码:
local c = nil
local d = 0
local e = 100
print(c and d)  -->打印 nil
print(c and e)  -->打印 nil
print(d and e)  -->打印 100
print(c or d)   -->打印 0
print(c or e)   -->打印 100
print(not c)    -->打印 true
print(not d)    -->打印 false
  • 注意:所有逻辑操作符将 false 和 nil 视作假,其他任何值视作真,对于 and 和 or,“短路求值”,对于 not,永远只返回 true 或者 false。

字符串连接

  • 在 Lua 中连接两个字符串,可以使用操作符“…”(两个点)。如果其任意一个操作数是数字的话,Lua 会将这个数字转换成字符串。注意,连接操作符只会创建一个新字符串,而不会改变原操作数。也可以使用 string 库函数 string.format 连接字符串。
  • 示例代码:
print("Hello " .. "World")    -->打印 Hello World
print(0 .. 1)                 -->打印 01

str1 = string.format("%s-%s","hello","world")
print(str1)              -->打印 hello-world

str2 = string.format("%d-%s-%.2f",123,"world",1.21)
print(str2)              -->打印 123-world-1.21
  • 由于 Lua 字符串本质上是只读的,因此字符串连接运算符几乎总会创建一个新的(更大的)字符串。这意味着如果有很多这样的连接操作(比如在循环中使用 … 来拼接最终结果),则性能损耗会非常大。在这种情况下,推荐使用 table 和 table.concat() 来进行很多字符串的拼接,例如:
local pieces = {}
for i, elem in ipairs(my_list) do
    pieces[i] = my_process(elem)
end
local res = table.concat(pieces)
  • 当然,上面的例子还可以使用 LuaJIT 独有的 table.new 来恰当地初始化 pieces 表的空间,以避免该表的动态生长。

优先级

  • Lua 操作符的优先级如下表所示(从高到低)
优先级
^
not、#、-
*、/、%
+、-
<、>、<=、>=、==、~=
and
or
  • 示例代码:
local a, b = 1, 2
local x, y = 3, 4
local i = 10
local res = 0
res = a + i < b / 2 + 1  -->等价于res =  (a + i) < ((b/2) + 1)
res = 5 + x ^ 2 * 8      -->等价于res =  5 + ((x^2) * 8)
res = a < y and y <= x   -->等价于res =  (a < y) and (y <= x)
  • 若不确定某些操作符的优先级,就应显示地用括号来指定运算顺序。这样做还可以提高代码的可读性。

控制语句

  • 流程控制语句对于程序设计来说特别重要,它可以用于设定程序的逻辑结构。一般需要与条件判断语句结合使用。Lua 语言提供的控制结构有 if、while、repeat、for,并提供 break关键字来满足更丰富的需求。

if…else

  • if-else 是我们熟知的一种控制结构。Lua 跟其他语言一样,提供了 if-else 的控制结构。

单分支if

x = 10
if x > 0 then
    print("x is a positive number")
end

两个分支if…else

x = 10
if x > 0 then
    print("x is a positive number")
else
    print("x is a non-positive number")
end

多分支if…elseif…else

score = 90
if score == 100 then
    print("Very good!Your score is 100")
elseif score >= 60 then
    print("Congratulations, you have passed it,your score greater or equal to 60")
    --此处可以添加多个elseif
else
    print("Sorry, you do not pass the exam! ")
end
  • 与 C 语言的不同之处是 else 与 if 是连在一起的,若将 else 与 if 写成 “else if” 则相当于在 else 里嵌套另一个 if 语句,如下代码:
score = 0
if score == 100 then
    print("Very good!Your score is 100")
elseif score >= 60 then
    print("Congratulations, you have passed it,your score greater or equal to 60")
else
    if score > 0 then
        print("Your score is better than 0")
    else
        print("My God, your score turned out to be 0")
    end --与上一示例代码不同的是,此处要添加一个end
end

while

  • Lua 跟其他常见语言一样,提供了 while 控制结构,语法上也没有什么特别的。但是没有提供 do-while 型的控制结构,但是提供了功能相当的 repeat
  • while 型控制结构语法如下,当表达式值为假(即 false 或 nil)时结束循环。也可以使用 break 语言提前跳出循环。
while 表达式 do
--body
end
  • 示例代码:
x = 1
sum = 0

while x <= 5 do
    sum = sum + x
    x = x + 1
end
print(sum)  -->output 15
  • 值得一提的是,Lua 并没有像许多其他语言那样提供类似 continue 这样的控制语句用来立即进入下一个循环迭代(如果有的话)。因此,我们需要仔细地安排循环体里的分支,以避免这样的需求。
  • 没有提供 continue,却也提供了另外一个标准控制语句 break,可以跳出当前循环。例如我们遍历 table,查找值为 11 的数组下标索引:
local t = {1, 3, 5, 8, 11, 18, 21}

local i
for i, v in ipairs(t) do
    if 11 == v then
        print("index[" .. i .. "] have right value[11]")
        break
    end
end

repeat

  • Lua 中的 repeat 控制结构类似于其他语言(如:C++ 语言)中的 do-while,但是控制方式是刚好相反的。简单点说,执行 repeat 循环体后,直到 until 的条件为真时才结束,而其他语言(如:C++ 语言)的 do-while 则是当条件为假时就结束循环。
  • 以下代码将会形成死循环:
x = 10
repeat
    print(x)
until false  -- until的条件一直为假,循环不会结束
  • 除此之外,repeat 与其他语言的 do-while 基本是一样的。同样,Lua 中的 repeat 也可以在使用 break 退出。

for

  • Lua 提供了一组传统的、小巧的控制结构,包括用于条件判断的 if 用于迭代的 while、repeat 和 for。
  • for 语句有两种形式:数字 for(numeric for)和范型 for(generic for)

for数字型

  • 语法:
for var = begin, finish, step do
    --body
end
  • 关于数字 for 需要关注以下几点:
    • var 从 begin 变化到 finish,每次变化都以 step 作为步长递增 var;
    • begin、finish、step 三个表达式只会在循环开始时执行一次;
    • 第三个表达式 step 是可选的,默认为 1;
    • 控制变量 var 的作用域仅在 for 循环内,需要在外面控制,则需将值赋给一个新的变量;
    • 循环过程中不要改变控制变量的值,那样会带来不可预知的影响。
  • 示例代码:
for i = 1, 5 do
  print(i)
end

-- output:
1
2
3
4
5
for i = 1, 10, 2 do
  print(i)
end

-- output:
1
3
5
7
9
  • 以下是这种循环的一个典型示例:
for i = 10, 1, -1 do
    print(i)
end

-- output:
10
9
8
7
6
5
4
3
2
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泛型

  • 泛型 for 循环通过一个迭代器(iterator)函数来遍历所有值
-- 打印数组a的所有值
local a = { "a", "b", "c", "d" }
for i, v in ipairs(a) do
    print("index:", i, " value:", v)
end

-- output:
index:  1  value: a
index:  2  value: b
index:  3  value: c
index:  4  value: d
  • Lua 的基础库提供了 ipairs,这是一个用于遍历数组的迭代器函数。在每次循环中,i 会被赋予一个索引值,同时 v 被赋予一个对应于该索引的数组元素值。
  • 下面是另一个类似的示例,演示了如何遍历一个 table 中所有的 key
-- 打印table t中所有的key
for k in pairs(t) do
    print(k)
end
  • 从外观上看泛型 for 比较简单,但其实它是非常强大的。通过不同的迭代器,几乎可以遍历所有的东西, 而且写出的代码极具可读性。标准库提供了几种迭代器,包括用于迭代文件中每行的(io.lines)、 迭代 table 元素的(pairs)、迭代数组元素的(ipairs)、迭代字符串中单词的(string.gmatch)等。
  • 泛型 for 循环与数字型 for 循环有两个相同点: (1)循环变量是循环体的局部变量; (2)决不应该对循环变量作任何赋值。
  • 对于泛型 for 的使用,再来看一个更具体的示例。假设有这样一个 table,它的内容是一周中每天的名称:
local days = {
  "Sunday", "Monday", "Tuesday", "Wednesday",
  "Thursday", "Friday", "Saturday"
}
  • 现在要将一个名称转换成它在一周中的位置。为此,需要根据给定的名称来搜索这个 table。然而 在 Lua 中,通常更有效的方法是创建一个“逆向 table”。例如这个逆向 table 叫 revDays,它以 一周中每天的名称作为索引,位置数字作为值:
local revDays = {
    ["Sunday"] = 1,
    ["Monday"] = 2,
    ["Tuesday"] = 3,
    ["Wednesday"] = 4,
    ["Thursday"] = 5,
    ["Friday"] = 6,
    ["Saturday"] = 7
}
  • 接下来,要找出一个名称所对应的位置,只需用名字来索引这个 reverse table 即可
local x = "Tuesday"
print(revDays[x])  -->3
  • 当然,不必手动声明这个逆向 table,而是通过原来的 table 自动地构造出这个逆向 table
local days = {
    "Monday", "Tuesday", "Wednesday", "Thursday",
    "Friday", "Saturday", "Sunday"
}

local revDays = {}
for k, v in pairs(days) do
    revDays[v] = k
end

-- print value
for k, v in pairs(revDays) do
    print("k:", k, " v:", v)
end

-- output:
k:  Tuesday   v: 2
k:  Monday    v: 1
k:  Sunday    v: 7
k:  Thursday  v: 4
k:  Friday    v: 5
k:  Wednesday v: 3
k:  Saturday  v: 6
  • 这个循环会为每个元素进行赋值,其中变量 k 为 key(1、2、…),变量 v 为 value(“Sunday”、“Monday”、…)。
  • 值得一提的是,在 LuaJIT 2.1 中,ipairs() 内建函数是可以被 JIT 编译的,而 pairs() 则只能被解释执行。因此在性能敏感的场景,应当合理安排数据结构,避免对哈希表进行遍历。事实上,即使未来 pairs 可以被 JIT 编译,哈希表的遍历本身也不会有数组遍历那么高效,毕竟哈希表就不是为遍历而设计的数据结构。

break、return、goto

break

  • 语句 break 用来终止 whilerepeatfor 三种循环的执行,并跳出当前循环体, 继续执行当前循环之后的语句。下面举一个 while 循环中的 break 的例子来说明:
-- 计算最小的x,使从1到x的所有数相加和大于100
sum = 0
i = 1
while true do
    sum = sum + i
    if sum > 100 then
        break
    end
    i = i + 1
end
print("The result is " .. i)  -->output:The result is 14
  • 在实际应用中,break 经常用于嵌套循环中。

return

  • return 主要用于从函数中返回结果,或者用于简单的结束一个函数的执行。 关于函数返回值的细节可以参考 函数的返回值 章节。return 只能写在语句块的最后,一旦执行了 return 语句,该语句之后的所有语句都不会再执行。若要写在函数中间,则只能写在一个显式的语句块内,参见示例代码:
local function add(x, y)
    return x + y
    --print("add: I will return the result " .. (x + y))
    --因为前面有个return,若不注释该语句,则会报错
end

local function is_positive(x)
    if x > 0 then
        return x .. " is positive"
    else
        return x .. " is non-positive"
    end

    --由于return只出现在前面显式的语句块,所以此语句不注释也不会报错
    --,但是不会被执行,此处不会产生输出
    print("function end!")
end

local sum = add(10, 20)
print("The sum is " .. sum)  -->output:The sum is 30
local answer = is_positive(-10)
print(answer)                -->output:-10 is non-positive
  • 有时候,为了调试方便,我们可以想在某个函数的中间提前 return,以进行控制流的短路。此时我们可以将 return 放在一个 do ... end 代码块中,例如:
local function foo()
    print("before")
    do return end
    print("after")  -- 这一行语句永远不会执行到
end

goto

  • LuaJIT 一开始对标的是 Lua 5.1,但渐渐地也开始加入部分 Lua 5.2 甚至 Lua 5.3 的有用特性。 goto 就是其中一个不得不提的例子。
  • 有了 goto,我们可以实现 continue 的功能:
local sum = add(10, 20)
print("The sum is " .. sum)  -->output:The sum is 30
local answer = is_positive(-10)
print(answer)                -->output:-10 is non-positive

for i = 1, 3 do
    if i <= 2 then
        print(i, "yes continue")
        goto continue
    end

    print(i, " no continue")

    :: continue ::
    print([[i'm end]])
end
  • 输出结果:
1   yes continue
i'm end
2   yes continue
i'm end
3    no continue
i'm end
  • GotoStatement 这个页面上,你能看到更多用 goto 玩转控制流的脑洞。
  • goto 的另外一项用途,就是简化错误处理的流程。有些时候你会发现,直接 goto 到函数末尾统一的错误处理过程,是更为清晰的写法。
local function process(input)
    print("the input is", input)
    if input < 2 then
        goto failed
    end
    -- 更多处理流程和 goto err

    print("processing...")
    do
        return
    end
    :: failed ::
    print("handle error with input", input)
end

process(1)
process(3)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值