Lua是一种轻量级、高效的脚本语言,最初由巴西的PUC-Rio大学于1993年开发。其名字在葡萄牙语中意为“月亮”。Lua的设计目标是提供一个可扩展的,易于嵌入到其他应用程序中的脚本语言。
特点
- 简洁性:Lua的语法简单易懂,学习曲线平缓,适合初学者。
- 高效性:Lua具有高效的解释器,执行速度快,内存占用少,适合资源有限的环境。
- 可扩展性:Lua允许用户通过C/C++等语言扩展功能,支持自定义数据结构和算法。
- 兼容性:可与多种编程语言和操作系统很好地兼容,常用于游戏开发、嵌入式系统等领域。
- 协程支持:Lua提供了协程的支持,可以实现轻量级的并发编程。
应用场景
- 游戏开发:Lua被广泛应用于游戏引擎中,例如Unity、CryEngine等,作为游戏脚本语言。
- 嵌入式系统:由于其轻量级特性,Lua常用于嵌入式设备和IoT(物联网)应用。
- Web 开发:一些服务器端框架和云服务也应用了Lua。
- 科学计算:与科学计算库结合使用,提高计算效率。
Lua有丰富的库和模块可以使用,称为“LuaRocks”,是Lua的包管理器。许多大型项目使用Lua作为其脚本语言,像Neovim、WezTerm等常用工具。
环境安装
Lua 的运行环境比较简单,可以去官网下载源文件手动编译安装,或者直接使用各平台的包管理器下载安装。
例如在 MacOS 直接使用 brew 进行安装
$ brew install lua
$ lua ./main.lua # 运行lua程序
语法学习
Hello World
从学习 C 开始,打印Hello World
已经成了学习一门新语言的第一件事,在 Lua 中打印需要通过print
方法进行输出。
print('Hello World') -- Hello World
Lua 中使用
--
表示单行注释,使用--[[]]
表示多行注释
类型
了解类型也是学习新语言首先要做的事,掌握了各种类型才能在编程时不出错误,好消息是 Lua 是一门弱类型语言(钱端仔狂喜),但是这也意味之开发者需要更加注意变量的操作。
类型 | 描述 | 示例 |
---|---|---|
nil | 表示无值或不存在的值,可以理解为NULL,变量未进行赋值默认是nil | type(nil) |
boolean | 布尔值,true 和 false | type(true) |
number | 数字类型,在 Lua3.0 之后只有双精度浮点(double)的数字 | type(1) |
string | 字符串,Lua 字符串同时支持单引号和双引号包裹,同时可以使用[[]] 表示跨行字符串 |
type(‘str’) |
function | 函数,除了声明语法不同,在使用上和 JS 函数基本一致 | type(print) |
table | 表类型,类比 JS 的对象类型,可以用来实现数组、字典等复杂类型 | type({}) |
thread | 线程类型, 表示协同程序(coroutine),用于实现非抢占式的多任务 | type(coroutine.create(fun)) |
userdata | 用户数据,允许用户在 Lua 中使用 C 语言的结构体 | type(io.stdin) |
Lua 提供了
type
函数用于判断变量的类型,接收一个参数,返回参数的类型字符串。
Lua 中有两种类型的变量,全局变量和局部变量,这两个概念在 JS 中也有,并且是类似的。在声明方式上:全局变量不需要使用关键字(a = 1
)局部变量使用local
关键字(local b = 2
),生效范围同 JS 一样按照函数作用域区分。
Lua 支持使用,
分割变量(多余忽略,少的补nil),例如 local a, b = 1, 2
,这使得在 Lua 中交换变量也变得尤为简单 a, b = b, a
。
Lua 中也存在“隐式和显示”两种形态的类型转换
- string -> number: 在算数运算中,字符串将被转为数字,如果字符串不是一个合法数字将会报错;通过
tonumber
方法显式转换; - number -> string:通过
..
拼接字符串(单独的字符串拼接关键字,比JS混用+
要好些),可以将number类型转为string进行拼接;通过tostring
方法显示转换; - boolean -> number:可以通过表达式
local num = value and 1 or 0
将value转为数字1或0; - number -> boolean:在 Lua 中假值只有false和nil,所以所有的number都可作为true使用,不需要转换;
表类型
表类型和 JS object 极为相似,通过字面量{}
进行声明,初始化赋值方式有两种
使用默认索引,使用场景类似数组,索引从1开始
local t = {1, 2, 3, 4}
for k, v in pairs(t) do
print(k, v)
end
-- 1 1
-- 2 2
-- 3 3
-- 4 4
手动声明key=value,使用场景类似map
local t = {a = 1, b = 2}
for k, v in pairs(t) do
print(k, v)
end
-- b 2
-- a 1
两种方式可以混用
local t = {a = 1, b = 2, 3, 4}
for k, v in pairs(t) do
print(k, v)
end
-- 1 3
-- 2 4
-- b 2
-- a 1
在访问 table 内容时和 JS 访问对象属性一致
local t = {a = 1, b = 2, 3, 4}
local key = 'a'
print(t[key], t.b) -- 1 2
print(t[1], t[2]) -- 3 4
函数类型
Lua 声明函数的方式如下,要在函数结束的时候标记一下end
function fun(arg1, arg2)
return arg1 + arg2
end
print(fun(123, 321)) -- 444
大部分语言的函数都是类似的形态,接收参数处理之后返回结果,特殊的是 Lua 中支持返回多个返回值
function fun(arg1, arg2)
return arg1 + arg2, arg1 - arg2
end
local sum, sub = fun(100, 20)
print(sum, sub) -- 120 80
同样支持不定项的参数
function fun(a, ...)
print(...) -- 3 5 7 9 11
end
fun(1, 3, 5, 7, 9, 11)
方法速查表
string
方法 | 描述 | 示例 |
---|---|---|
string.upper(str) | 将字符串转为大写 | string.upper(‘abcd’) |
string.lower(str) | 将字符串转为小写 | string.lower(‘ABCD’) |
string.gsub(str, target, newStr[, count]) | 替换字符串中指定的字符,通过第四个参数指定替换的个数,默认全部替换 | string.gsub(‘aaaa’, ‘a’, ‘b’, 3) |
string.find(str, target[, startIndex]) | 查找字符串中的指定字符串索引,可以通过第三个参数指定起始位置 | string.find(‘abcd’, ‘bc’) |
string.reverse(str) | 反转字符串 | string.reverse(‘abcd’) |
string.format(template, …value) | 格式化字符串中的占位符 | string.format(‘Hello %s’, ‘Lua’) |
string.char(…code) | 将ASCII编码转为字符串 | string.char(97,98,99,100) |
string.byte(str[, index]) | 将指定位置的字符转为ASCII编码,默认第一个字符 | string.byte(‘abcd’, 3) |
string.len(str) | 计算字符串的长度 | string.len(‘abcd’) |
string.rep(str, count) | 返回重复指定次数的字符串 | string.rep(‘abc’, 3) |
string.sub(str, index[, lenfth]) | 从指定位置剪切字符串,默认截取到字符串末端 | string.sub(‘Hello Lua’, 2) |
format
格式化的模板中使用了格式控制符,常见的格式控制符有以下几个:
- %s:接收一个字符串转为字符串
- %d:接收一个数字转为有符号整形数字
- %f:接收一个数字转为浮点类型,默认保留6位,不足6位补0
- %x:接收一个数字转为小写的十六进制
- %X:接收一个数字转为大写的十六进制
math
数学方法是针对数字之间运算的补充能力,Lua中提供了以下方法
方法/属性 | 描述 |
---|---|
math.huge | 一个比任何数字都大的浮点数 |
math.mininteger | 最小值的整数 |
math.abs(num) | 取绝对值 |
math.ceil(num) | 向上取整 |
math.floor(num) | 向下取整 |
math.fmod(num1, num2) | 取余 |
math.max(…num) | 比较最大值 |
math.min(…num) | 比较最小值 |
math.sqrt(num) | 平方根 |
math.type(num) | 返回数字的类型float 、integer |
math.log(log, base) | 返回以base为底,log的对数 |
math.exp(num) | 返回以e为底的自然对数 |
math.random([num1[, num2]]) | 返回随机数,默认[0,1) 区间;一个参数时区间为[1, num1];两个参数时区间为[num1, num2] |
math.ult(num1, num2) | 无符号整数比较 |
math.tointeger(arg) | 将参数转为整形,无法转为整形时返回nil |
math.modf(num) | 返回整数和小数部分 |
math.pi | 返回圆周率 |
math.sin(num) | 正弦函数 |
math.cos(num) | 余弦函数 |
math.tan(num) | 正切函数 |
math.asin(num) | 反正弦函数 |
math.acos(num) | 反余弦函数 |
math.atan(num) | 反正切函数 |
math.rad(num) | 角度转为弧度 |
math.deg(num) | 弧度转为角度 |
流程控制
条件语句————使程序具备决策能力,能够根据条件执行不同的代码;循环语句————允许程序多次执行某段代码,简化复杂的重复操作。合理使用条件语句和循环语句可以提高代码的可读性和维护性,形成更灵活的程序结构。
逻辑运算符和关系运算符是流程控制中重要的组成部分,在 Lua 中支持的运算符如下
类型 | 表达式 | 含义 |
---|---|---|
逻辑 | and | 逻辑且 |
逻辑 | or | 逻辑或 |
逻辑 | not | 逻辑非 |
关系 | == | 等于 |
关系 | ~= | 不等于 |
关系 | > | 大于 |
关系 | >= | 大于等于 |
关系 | < | 小于 |
关系 | <= | 小于等于 |
条件语句通过if...elseif...else
控制,要注意的是每一个if/elseif
表达式结束之后要手动添加then
关键字,在整个条件语句结束要添加end
关键字,例如
local function func(num)
if (num >= 90)
then
return 'A'
elseif (num >= 80)
then
return 'B'
elseif (num >= 60)
then
return 'C'
else
return 'D'
end
end
print(func(60)) -- C
与其他主流语言中的循环语句类似,Lua 中也支持三种形态的循环控制
for 循环,lua 中的 for 循环从参数 1 变化到参数 2,每次变化以参数 3 为步长递增 i,并执行一次表达式;参数 3 是可选的,默认为 1; 参数二支持表达式,只会在循环开始执行时计算一次。
local f = function(x)
print("in f(x) ")
return x * 2
end
for i = 1, f(5) do
print(i)
end
while 循环
local num = 1
while (num < 5) do
print("num 的值为:", num)
num = num + 1
end
repeat…until 循环(相当于do…while循环),会在循环体执行完毕之后检查条件
local num = 11
repeat
print("num 的值为: ", num)
num = num + 1
until (num > 10)
循环跳出使用
break
关键字,但是不支持continue
关键字。
迭代器
迭代器的概念在 JS 中很常见,在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
常见的使用迭代器的场景是针对表类型的元素迭代,主要有两个方法pairs
(枚举所有的key-value)和ipairs
(枚举所有的数组项key-value)
local t = {
a = 1,
b = 2,
c = 3,
10,
20,
30
}
for key, value in pairs(t) do
print(string.format('property %s\'s value is %s', key, value))
end
for index, value in ipairs(t) do
print(string.format('The %sth value of the array is %s', index, value))
end
⚠️要注意的是,属性的遍历顺序是不固定的
模块
模块系统在编程语言中极为重要,好的模块系统可以:
- 组织代码结构:模块系统可以帮助开发者将代码按照功能或逻辑进行划分和组织,使得代码更加清晰、易于管理和维护。
- 提高可复用性:通过模块化的方式编写代码,可以将一些通用的功能封装成模块,在不同的项目中进行复用,提高了代码的可重用性。
- 降低耦合度:使用模块化设计可以降低不同部分之间的依赖关系,减少各个部分之间的耦合度,从而提高了代码的灵活性和可扩展性。
- 增强安全性:通过限制对外暴露接口和数据,并且采取适当措施保护内部实现细节,可以增强程序的安全性。
- 简化测试与调试:由于每个模块都是相对独立且较小规模的单元,在测试时可以更容易地定位问题所在,并且也更容易进行单元测试等操作。
Lua 的模块系统非常简洁,导出一个模块的方式如下
local a = 1
local function change(value)
a = value
end
local mod = {}
function mod.change(value)
change(value)
end
function mod.getA()
return a
end
return mod
声明一个表,可以在在表上添加属性和方法,最后返回这个表,上面的代码块通过私有方法的形式,防止外部直接修改数据。
引入模块也很简单,和NodeJS的commonjs引入模块类似
local mod = require('./module')
print(mod.getA()) -- 1
mod.change(2)
print(mod.getA()) -- 2
就日常作为配置工具而言,掌握上面这些已经基本可以满足了,如果是作为生产脚本开发,则需要再深入了解一下协同程序、C语言自定义类型(userdata)等内容。
后续如果有时间,可以深入写下深入的内容。