Lua
Table of Contents
1. Lua 简介
Lua 是一个小巧的脚本语言,它是巴西里约热内卢天主教大学里的一个由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 三人所组成的研究小组于 1993 年开发的。Lua 中不用关心内存的分配和释放,有 GC 机制。
1.1. Hello World 程序
Lua 版本的 Hello World 程序如下:
print("Hello World")
假定你把上面这句保存在 hello.lua 文件中,你在命令行只需要:
$ lua hello.lua
2. Lua 基本介绍
2.1. 注释
两个连字符 --
是单行注释,如:
-- 这是单行注释
多行注释以 --[[
开始,以 --]]
结束,如下面代码中 print(10)
不会输出任何内容:
--[[ print(10) -- no action (commented out) --]]
如果我们想临时取消多行注释,让被注释掉的代码重新生效,有一个技巧就是在多行注释的开始位置多加一个连字符(即 3 个连字符),如:
---[[ print(10) --> 10 --]]
这是因为, ---[[
表示一个普通的单行注释,而接下来的 --]]
也是单行注释。
2.2. 标示符和保留字
Lua 标示符用于定义一个变量,函数或者其他用户定义的项。
下面这些关键字是保留字(不能作为标示符使用):
and break do else elseif end false for function goto if in local nil not or repeat return then true until while
作为约定,以下划线开头后面根大写字母的名字(比如 _VERSION
)被保留用于 Lua 内部全局变量。
2.3. Chunks
Chunk 就是一个代码块(Block),它由一个或多个语句组成。比如使用 Lua 解释器执行的一个完整 Lua 源码文件就是一个 Chunk。在交互模式下,Lua 通常把每一个行当作一个 Chunk,但如果 Lua 一行不是一个完整的 Chunk 时,他会等待继续输入直到得到一个完整的 Chunk。
The unit of compilation of Lua is called a chunk.
A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first loads it, precompiling the chunk's code into instructions for a virtual machine, and then Lua executes the compiled code with an interpreter for the virtual machine.
Chunks can also be precompiled into binary form; see program luac and function string.dump for details.
2.4. 全局变量
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是: nil
.
print(b) --> nil b = 10 print(b) --> 10
如果你想删除一个全局变量,只需要将变量赋值为 nil
.
b = nil print(b) --> nil
2.5. 局部变量
默认地,变量是全局变量。使用 local
可以创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。如:
x = 10 local i = 1 -- local to the chunk while i<=x do local x = i*2 -- local to the while body print(x) --> 2, 4, 6, 8, ... i = i + 1 end
2.6. 赋值语句
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
a, b = 10, 2*x -- 相当于 a=10; b=2*x
遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[i]'
当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
1、“变量个数”大于“值的个数”时,按变量个数补足 nil
2、“变量个数”小于“值的个数”时,多余的值会被忽略。
变量个数和值的个数不一致时的例子:
a, b, c = 0, 1 print(a,b,c) --> 0 1 nil a, b = a+1, b+1, b+2 --> value of b+2 is ignored print(a,b) --> 1 2
2.7. Metatables and Metamethods
Every value in Lua can have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field "__add" of the value's metatable. If it finds one, Lua calls this function to perform the addition.
The key for each event in a metatable is a string with the event name prefixed by two underscores; the corresponding values are called metamethods. In the previous example, the key is "__add" and the metamethod is the function that performs the addition. Unless stated otherwise, metamethods should be function values.
3. 类型和值
Lua 是动态类型语言,这意味着变量没有类型,只有值有类型。
Lua 中所有的值都是“一等公民”。 这意味着所有的值均可保存在变量中、当作参数传递给其它函数、以及作为返回值。
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table,如表 1 所示。
数据类型 | 描述 |
---|---|
nil | 只有值 nil 属于该类,表示一个无效值(在条件表达式中相当于 false)。全局变量没有被赋值前默认值为 nil |
boolean | 包含两个值:false 和 true。Lua 中所有值都可以作为条件, 除了 false 和 nil 为假,其它值都是真(注:数字 0 和空字符串也是真) |
number | 整数或浮点数(默认为 64 位整数或双精度浮点数) |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 这是宿主程序中的数据,Lua 中不能创建和修改 userdata,只能通过 C API 操作它们 |
thread | Lua 中的线程,用于实现 coroutines。Lua 线程不同于操作系统的线程,在不支持线程的操作系统也有 Lua coroutines. |
table | Lua 中的 table 其实是一个“关联数组”,数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过“构造表达式”来完成,最简单构造表达式是 {} ,用来创建一个空表。 |
3.1. 字符串
字符串由一对双引号(支持转义)或单引号(不支持转义)来表示。
Lua 中字符串是不可变的, 我们不能改变字符串中的某个字符。
a = "one string" b = string.gsub(a, "one", "another") -- change string parts print(a) --> one string print(b) --> another string
使用 #
符号可以得到字符串的字节长度(不是某种编码下字符的数量)。如:
a = "hello" print(#a) --> 5 print(#"good bye") --> 8 print(#"中文") --> 6 print(utf8.len("中文")) --> 2 (函数 utf8.len 可以得到utf8字符串的字符数量)
使用 ..
可以连接两个字符串(如果是数字,则会先转换为字符串),得到新字符串,如:
print("Hello " .. "World") --> Hello World print("result is " .. 3) --> result is 3
还可以使用 [[...]]
表示字符串。这种形式的字符串可以包含多行,且不会解释转义序列,如果 [[
后的第一个字符是换行符会被自动忽略掉。如:
page = [[ <html> <head> <title>An HTML Page</title> </head> <body> <a href="http://www.lua.org">Lua</a> </body> </html> ]] print(page)
3.2. Table(关联数组)
Table 是 Lua 中主要的数据结构,它是关联数组。
可以使用 {}
创建 Table,如:
a = {} a["x"] = 10 a.y = "hello" -- 相当于 a["y"] = "hello" print(a.x) -- 输出 10 print(a["x"]) -- 同上,输出 10 print(a.y) -- 输出 "hello" print(a.z) -- 访问不存在的下标,也不会报错,会输出 nil
使用 {}
也可以创建数组,如:
days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} -- 相当于 days = {[1]="Sunday", [2]="Monday", [3]="Tuesday", [4]="Wednesday", [5]="Thursday", [6]="Friday", "Saturday"} print(days[1]) -- 输出Sunday,lua中数组默认从下标1(而不是0)开始
3.2.1. pairs 遍历 Table
使用 pairs
可以遍历 Table 中的 key-value 对(遍历顺序不固定),如:
t = {10, x = 12, k = "hi"} for k, v in pairs(t) do print(k, v) end
上面程序可能输出:
1 10 x 12 k hi
特别说明,多次运行上面程序,可能得到不同结果。如:
1 10 k hi x 12
3.2.2. ipairs 遍历 Table
使用 ipairs
可以遍历数组(遍历顺序是固定的),如:
t = {10, 12, "hi"} for k, v in ipairs(t) do print(k, v) end
上面程序会输出:
1 10 2 12 3 hi
长度操作符 #
可以得到数组长度,所有也可以采用下面方式来遍历数组,如:
t = {10, 12, "hi"} for k = 1, #t do print(k, t[k]) end
上面程序会输出:
1 10 2 12 3 hi
4. 运算符
Lua 支持下面算术运算符:
+
: addition
-
: subtraction
*
: multiplication
/
: float division
//
: floor division
%
: modulo
^
: exponentiation
-
: unary minus
Lua 支持下面位运算符:
&
: bitwise AND
|
: bitwise OR
~
: bitwise exclusive OR
>>
: right shift
<<
: left shift
~
: unary bitwise NOT
Lua 支持下面关系运算符:
==
: equality
~=
: inequality
<
: less than
>
: greater than
<=
: less or equal
>=
: greater or equal
Lua 支持 3 个逻辑运算符: and
, or
, not
5. 控制结构
5.1. if 语句
if
语句有 3 种形式,分别如下:
if conditions then then-part end if conditions then then-part else else-part end if conditions then then-part elseif conditions then then-part ...... ---> 多个elseif else else-part end
如:
a = 10 if a < 20 then print("a < 20") else print("a >= 20") end
5.2. while 循环
下面是 while
循环的例子:
a=1 while a < 5 do print(a) -- 输出1到4 a = a+1 end
5.3. for 循环
for
循环有两种形式,第一种形式如下:
for var=exp1, exp2[, exp3] do your code block end
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长(exp3 是可选的,如果不指定,默认为 1)递增 var,并执行一次 block。如:
for i=1, 5 do -- 省略了步长,默认为1 print(i) -- 从1输出到5 end for i=5, 1, -1 do -- 步长是 -1,从5输出到1 print(i) end
for
循环的第二种形式为(类似于 Java 中的 foreach):
for namelist in explist do your code block end
下面是 for
循环的第二种形式的一个实例:
a = {"one", "two", "three"} for i, v in ipairs(a) do -- ipairs是Lua的一个迭代器函数,用来迭代数组 print(i, v) end
上面代码将输出:
1 one 2 two 3 three
5.4. repeat...until 循环
repeat...until
循环的条件语句在当前循环结束后判断,所以被包围的代码块至少会执行一次。如:
a=1 repeat print(a) -- 输出1到4 a = a+1 until a >= 5
5.5. break 和 return 语句
break
语句用来退出当前循环(即在 for, repeat, while 语句中使用),在循环外部不可以使用。注:Lua 中没有 continue 语句。
return
用来从函数返回结果,当一个函数自然结束结尾会有一个默认的 return。
Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:或者在 end 之前,或者 else 前,或者 until 前) ,如:
local i = 1 while a[i] do if a[i] == v then break end i = i + 1 end
有时候为了调试方便或者其他目的需要在 block 的中间使用 return 或者 break, 可以显式的使用 do ... end
来实现, 如:
function foo () return --<< SYNTAX ERROR do return end -- OK ... -- statements not reached end
5.6. goto 语句
goto
语句将程序的控制点转移到一个标签处,标签名由 ::
开始和结束。如下面程序将输出 1 到 10 中的奇数:
for i=1,10 do if i % 2 == 0 then goto labal1 -- 这个goto实现了continue的效果 end print(i) ::labal1:: end
6. 函数
Lua 中函数以关键字 function
开始,如:
function max(num1, num2) if num1 > num2 then return num1 else return num2 end end print(max(1, 3))
也可以写为形式:
max = function (num1, num2) if num1 > num2 then return num1 else return num2 end end
6.1. 返回多个值
Lua 函数可以返回多个结果值,比如 string.find
,其返回匹配串“开始和结束的下标”(如果不存在匹配串则返回 nil)。
s, e = string.find("hello Lua users", "Lua") print(s, e) --> 7 9
Lua 函数中,在 return 后列出要返回的值得列表即可返回多值,如:
function maximum (a) local mi = 1 -- maximum index local m = a[mi] -- maximum value for i,val in ipairs(a) do if val > m then mi = i m = val end end return m, mi end print(maximum({8,10,23,12,5})) --> 23 3
Lua 总是调整函数返回值的个数去适用调用环境,当作为一个语句调用函数时,所有返回值被忽略。假设有如下三个函数:
function foo0 () end -- returns no results function foo1 () return "a" end -- returns 1 result function foo2 () return "a", "b" end -- returns 2 results
下面是调用实例:
x,y = foo0() -- x=nil, y=nil x,y = foo1() -- x="a", y=nil x,y,z = foo2() -- x="a", y="b", z=nil x = foo2() -- x='a', 'b' is discarded
6.2. 可变参数
Lua 中,函数最后一个参数使用三点 ...
表示函数有可变的参数。如:
function add(...) local s = 0 for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组 s = s + v end return s end print(add(3,4,5,6,7)) --> 25
7. Lua 完整语法(Extended BNF)
Lua 的语法比较简洁,完整的 Extended BNF 如下:
chunk ::= block block ::= {stat} [retstat] stat ::= ';' | varlist '=' explist | functioncall | label | break | goto Name | do block end | while exp do block end | repeat block until exp | if exp then block {elseif exp then block} [else block] end | for Name '=' exp ',' exp [',' exp] do block end | for namelist in explist do block end | function funcname funcbody | local function Name funcbody | local namelist ['=' explist] retstat ::= return [explist] [';'] label ::= '::' Name '::' funcname ::= Name {'.' Name} [':' Name] varlist ::= var {',' var} var ::= Name | prefixexp '[' exp ']' | prefixexp '.' Name namelist ::= Name {',' Name} explist ::= exp {',' exp} exp ::= nil | false | true | Numeral | LiteralString | '...' | functiondef | prefixexp | tableconstructor | exp binop exp | unop exp prefixexp ::= var | functioncall | '(' exp ')' functioncall ::= prefixexp args | prefixexp ':' Name args args ::= '(' [explist] ')' | tableconstructor | LiteralString functiondef ::= function funcbody funcbody ::= '(' [parlist] ')' block end parlist ::= namelist [',' '...'] | '...' tableconstructor ::= '{' [fieldlist] '}' fieldlist ::= field {fieldsep field} [fieldsep] field ::= '[' exp ']' '=' exp | Name '=' exp | exp fieldsep ::= ',' | ';' binop ::= '+' | '-' | '*' | '/' | '//' | '^' | '%' | '&' | '~' | '|' | '>>' | '<<' | '..' | '<' | '<=' | '>' | '>=' | '==' | '~=' | and | or unop ::= '-' | not | '#' | '~'
8. 参考
Lua 5.3 Reference Manual: https://www.lua.org/manual/5.3/manual.html
Programming in Lua, 4th Edition