Lua是一种轻量级、高效的脚本语言,最初由巴西的PUC-Rio大学于1993年开发。其名字在葡萄牙语中意为“月亮”。Lua的设计目标是提供一个可扩展的,易于嵌入到其他应用程序中的脚本语言。

特点

  1. 简洁性:Lua的语法简单易懂,学习曲线平缓,适合初学者。
  2. 高效性:Lua具有高效的解释器,执行速度快,内存占用少,适合资源有限的环境。
  3. 可扩展性:Lua允许用户通过C/C++等语言扩展功能,支持自定义数据结构和算法。
  4. 兼容性:可与多种编程语言和操作系统很好地兼容,常用于游戏开发、嵌入式系统等领域。
  5. 协程支持:Lua提供了协程的支持,可以实现轻量级的并发编程。

应用场景

  1. 游戏开发:Lua被广泛应用于游戏引擎中,例如Unity、CryEngine等,作为游戏脚本语言。
  2. 嵌入式系统:由于其轻量级特性,Lua常用于嵌入式设备和IoT(物联网)应用。
  3. Web 开发:一些服务器端框架和云服务也应用了Lua。
  4. 科学计算:与科学计算库结合使用,提高计算效率。

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) 返回数字的类型floatinteger
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

⚠️要注意的是,属性的遍历顺序是不固定的

image-20240818125452849

模块

模块系统在编程语言中极为重要,好的模块系统可以:

  1. 组织代码结构:模块系统可以帮助开发者将代码按照功能或逻辑进行划分和组织,使得代码更加清晰、易于管理和维护。
  2. 提高可复用性:通过模块化的方式编写代码,可以将一些通用的功能封装成模块,在不同的项目中进行复用,提高了代码的可重用性。
  3. 降低耦合度:使用模块化设计可以降低不同部分之间的依赖关系,减少各个部分之间的耦合度,从而提高了代码的灵活性和可扩展性。
  4. 增强安全性:通过限制对外暴露接口和数据,并且采取适当措施保护内部实现细节,可以增强程序的安全性。
  5. 简化测试与调试:由于每个模块都是相对独立且较小规模的单元,在测试时可以更容易地定位问题所在,并且也更容易进行单元测试等操作。

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)等内容。

后续如果有时间,可以深入写下深入的内容。


前端小白