0%

《lua 程序设计》读书笔记(1):Lua 语言入门

Lua 语言诞生于巴西的里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro,PUCRio)),其作者是 Roberto IerusalimschyWaldemar CelesLuiz Henrique de Figueiredo。Lua 在葡萄牙语中的含义是 月亮

Lua 语言从一开始就把简单、高效、可移植、可嵌入、可扩展等作为自己的目标。它专注于做一个配角,作为胶水语言来辅助像C、C++这样的主角来更好地完成工作。在主流编程语言越来越复杂、越来越追求大而全的今天,Lua 语言则始终恪守本分地做好 胶水语言 的本职工作。

Lua 语言从1993年诞生至今已 30 余年,是开源嵌入式脚本语言领域中一门独树一帜的语言。除了作为游戏引擎的嵌入式脚本语言,其在 Redis、Nginx/OpenResty、NAMP 等应用软件也有大量应用。它们将 Lua 作为其嵌入式脚本引擎,以供开发者进行功能扩展和二次开发等。

《Lua 程序设计》第 4 版主要针对的是 Lua 5.3。

前言

Lua 语言从一开始就被设计为能与 C/C++ 及其他常用语言开发的软件集成在一起使用的语言。Lua 语言支持组件化的软件开发方式,通过整合已有的高级组件构建新的应用。这些组件通常是通过C/C++等编译型强类型语言编写的,Lua语言充当了整合和连接这些组件的角色。因此通常将 Lua 作为一门胶水语言使用。此时:

  • 组件(或对象)是对程序开发过程中相对稳定逻辑的具体底层(如小部件和数据结构)的抽象,这些逻辑占用了程序运行时的大部分 CPU 时间
  • 而产品生命周期中可能经常发生变化的逻辑则可以使用 Lua 语言来实现

当然作为一门独立的编程语言,Lua 语言也同样适用于大中型项目。对于这些应用而言,Lua语言的主要能力源于标准库。而且第三方库的数量也在不断增加,可以通过 LuaRocks 来完成第三方 Lua 模块的部署和管理。

lua 开发环境搭建

首先直接安装 Lua 的当前最新版本 5.4.4:

1
2
3
4
5
curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz
tar zxf lua-5.4.4.tar.gz
cd lua-5.4.4
make all test
make install

确认安装成功:

1
2
# lua -v
Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio

之后为了在 nvim 中编写 lua 代码,需要为 lua 配置 language server,这样编辑代码时就可以有语法提示等功能了,具体方法为:

  • 创建任意一个空的 XXX.lua 文件,使用 nvim 打开该文件,执行 :LspInstall 命令,就会为 Lua 文件推荐所支持的 LS(Language Server)
  • 这里推荐的是 sumneko_lua,选择该 LS 即可自动安装
  • 安装完成之后,配置 nvim 使用该 LS:修改 ~/.config/nvim/lua/user/lsp/lsp-installer.lua 文件,在 servers 中增加 sumneko_lua

Lua 语言入门

在安装完成之后,还是从最简单的 hello world 开始:

1
print("hello world")
1
2
# lua hello_world.lua
hello world

下面则是一个计算阶乘的函数:

1
2
3
4
5
6
7
8
9
10
11
12
local function fact(n)
if n == 0 then
return 1
else
return n * fact(n - 1)
end

end

print("enter a number")
local a = io.read("*n")
print(fact(a))
1
2
3
4
# lua fact.lua
enter a number
5
120

程序段

Lua 语言执行的每一段代码(例如,一个文件或交互模式下的一行)称为一个程序段(Chunk),即一组命令或表达式组成的序列。程序段既可以简单到只由一句表达式构成,也可以由多句表达式和函数定义组成。

可以独立运行解释器,进入交互模式:

  • 从 Lua 5.3 版本开始,可以直接在交互模式下输入表达式,Lua 语言会输出表达式的值,而之前的版本则需要在表达式前加上一个等号才会输出表达式的值
  • 在交互模式下,如果 Lua 解释器发现输入的一行不完整,那么它会等待直到程序块或表达式被输入完整后再进行解释执行
  • -i 参数让 Lua 语言解释器在执行完指定的程序段后进入交互模式
  • 在交互模式下,可以通过调用函数 dofile 来执行一个 lua 程序文件
1
2
3
4
5
6
7
8
# lua
Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio
> 4 + 5
9
> = 4 + 5
9
> dofile("hello_world.lua")
hello world

一些词法规范

Lua 语言中的标识符(或名称)是由任意字母、数字和下划线组成的字符串,注意,不能以数字开头。Lua 也有些保留字,不能作为标识符,而且 Lua 语言是大小写敏感的。

  • 下划线+大写字母 组成的标识符通常被 Lua 语言用作特殊用途,应该避免使用
  • 下划线+小写字母 通常用作 dummy 变量(一种编程约定,不强制)

Lua语言中使用两个连续的连字符(–)表示单行注释的开始。使用两个连续的连字符加两对连续左方括号表示长注释或多行注释的开始,直到两个连续的右括号为止,中间都是注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print("line1")

-- print("commented")

--[[
print("commented")
]]

--[[
print("commented")
--]]

---[[
print("line2")
--]]

在实际使用过程中,通常使用 --[[ --]] 的方法来注释代码块,当我们需要重新启用这段代码时,只需要在第一行行首在添加一个 - 字符即可,这样 ---[[--]] 都会被当做两个单行注释,失去了多行注释的作用。

在 Lua 语言中,连续语句之间的分隔符并不是必需的,如果有需要的话可以使用分号来进行分隔。在Lua语言中,表达式之间的换行也不起任何作用。以下语句都是合法的:

1
2
3
4
5
6
7
8
9
a = 1
b = a * 2

a = 1;
b = a * 2;

a = 1; b = a * 2

a = 1 b = a * 2

通常,同一行中书写多条语句的情况下(这种情况一般也不会出现),才会使用分号做分隔符。

全局变量

在 Lua 语言中,全局变量(Global Variable)无须声明即可使用,使用未经初始化的全局变量也不会导致错误。当使用未经初始化的全局变量时,得到的结果是 nil。当把 nil 赋值给全局变量时,Lua会回收该全局变量(就像该全局变量从来没有出现过一样)。Lua 语言不区分未初始化变量和被赋值为 nil 的变量

类型和值

Lua 是一种动态类型语言,在这种语言中没有类型定义(type definition),每个值都带有其自身的类型信息。Lua 共有 8 种基本类型:

  • nil(空)
  • boolean(布尔)
  • number(数值)
  • string(字符串)
  • userdata(用户数据)
  • function(函数)
  • thread(线程)
  • table(表)

使用 type 可以获取一个值对应的类型名称(字符串):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> type(nil)
nil
> type(1)
number
> type('1')
string
> type(io.stdin)
userdata
> type(print)
function
> type({})
table
> type(type(nil))
string

变量没有预定义的类型,任何变量都可以包含任何类型的值。一般情况下,将一个变量用作不同类型时会导致代码的可读性不佳;但是,在某些情况下谨慎地使用这个特性可能会带来一定程度的便利。

nil 类型是一种只有一个 nil 值的类型,它的主要作用就是与其他所有值进行区分。Lua 使用 nil 值来标识无效值。

userdata 类型允许把任意的 C 语言数据保存在 Lua 语言变量中。在 Lua 中,用户数据类型除了赋值和相等性测试外,没有其他预定义的操作。用户数据被用来表示由应用或C语言编写的库所创建的新类型。

Boolean 类型具有两个值,true 和 false。Boolean 值并非是用于条件测试的唯一方式,任何值都可以表示条件。在Lua语言中,条件测试将除 Boolean 值 false 和 nil 外的所有其他值视为真

Lua 支持常见的逻辑运算符:andornot。同样,逻辑运算符将 Boolean 类型的 false 和 nil 当作假,而把其他值当作真。and or 都具有短路求值的特点,只在必要时才对第二个操作数进行求值。

  • and:如果它的第一个操作数为假,则返回第一个操作数,否则返回第二个操作数
  • or:如果她的第一个操作数为真,则返回第一个操作数,否则返回第二个操作
  • not 运算符永远返回 Boolean 类型的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> nil and 10
nil
> nil or 10
10
> 10 and nil
nil
> 10 or nil
10
> not nil
true
> not not nil
false
> not not false
false
> not not 0
true

Lua 中会有一些惯用方法:

  • x = x or v:等价于 if not x then x = v end
  • ((a and b) or c) 或者 (a and b or c):两者等价,因为 and 运算符优先级高于 or。当 b 不为假时,它等价于 C 语言的三目运算符 a ? b : c

其他类型则在后续文章详细介绍。

lua 解释器

在 POSIX 系统中,为了在脚本文件中指定解释器,可以使用 #! 语法,例如将 #!/usr/local/bin/lua 将 lua 作为解释器。

单独执行 lua 解释器命令时,其语法为 lua [options] [script [args]],其支持一些选项:

  • -e:直接在命令行中执行 lua 代码
  • -l:用来加载库
  • -i:用于在运行完其他命令行参数后进入交互模式
1
2
# lua -e "print('hello world')"
hello world

解释器在处理参数之前,会查找 LUA_INIT 等环境变量,可以通过这种方式配置 Lua:

  • 如果环境变量的值为 @filename,则解释器会执行对应的文件
  • 否则认为其包含 lua 代码,对其进行解释执行

可以通过预先定义的全局变量 arg 来获取解释器传入的参数。解释器在运行代码之前,会创建一个名为 arg 的表,它存储了所有的命令行参数:

  • 索引 0 保存了脚本名
  • 索引 1 保存了第一个参数、依此类推
  • 脚本之前所有的选项则位于负数索引上

Lua 语言也支持可变长参数,在脚本文件中,通过 ... 表示传递给脚本的所有参数。

Lua 解决八皇后问题

如下 lua 脚本对八皇后问题进行了求解。通过该脚本,可以对 Lua 语言的特点有个直观认识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
N = 8

function isplaceok(a, n, c)
for i = 1, n -1 do
if (a[i] == c) or
(a[i] - i == c - n) or
(a[i] + i == c + n) then
return false
end
end
return true
end


function printsolution(a)
for i = 1, N do
for j = 1, N do
io.write(a[i] == j and "X" or "-", " ")
end
io.write("\n")
end
io.write("\n")
end


function addqueue(a, n)
if n > N then
printsolution(a)
else
for c = 1, N do
if isplaceok(a, n, c) then
a[n] = c
addqueue(a, n + 1)
end
end
end
end

addqueue({}, 1)