Lua 基础
Lua
function fn (n)
if n == 0 then
return 1
else
return n * fn(n - 1)
end
end
print('please input number')
input = io.read('*number')
print(fn(input))
Lua 区分大小写,可选分号。
Lua 默认声明全局变量,访问未初始化的变量只会获得 nil,全局变量具有非零值时,它才存在。
Lua 中,除 false 和 nill 外的所有值,包括 0,'' 都是真值。
Lua 标识符可以是任意字母,数字,下划线组成字符串。不能以数字开头。且应该避免下划线加大写的形式,如 _VERSION,这些标识符在 Lua 中保留有特殊用途。
-- 一行 lua 注释
[[
多行注释
[=[
一级嵌套注释
[==[
二级嵌套注释
]==]
]=]
]]
类型和值
Lua 是动态类型的。有八种基本类型:
- nil 空值
- 当变量赋予 nil 时,可以视作变量不存在。全局变量默认为 nil。
- number 数字
- 实数(双精度浮点数)
- boolean 布尔
- 在 Lua 中,只有 false 和 nil 为假,其他包含 0 和 '' 在内的均为真。
- string 字符串
- 是不可变的。可以使用 [[ ]] 来获取多行文本。且 Lua 提供运行时的数字和字符串之间的转换。但需要注意,10 == '10' 总是错误的。
- userdata 用户数据
- 允许将任意的 C 数据结构存储在 Lua 变量中。
- 出赋值和相等以外,在 Lua 中没有预定义操作。
- function 函数
- 可以将函数存储在变量中,作为参数传递和接收。
- 可以调用 Lua 或 C 编写的函数。
- 应用可以用 C 定义其他函数。
- thread 协程
- table 表
- 实现关联数组。可以使用 nil 以外的其他值作为索引。且没有固定大小。
- 是 Lua 主要的数据结构机制。
- 通过构造函数表达式 {} 创建。
- 可以使用 t.name, t[name] 的形式方式作为索引。
- 使用整数索引将表作为传统数组使用。无法声明数组大小,只能初始化需要元素。且迭代时,第一个未初始化的索引将导致 nil,而 Lua 将第一个 nil 作为数组结尾。
- 此外,Lua 习惯用 1 作为数组开头。
表达式
Lua 支持常见的加减乘除和负数运算符。
Lua 支持 <、>、<=、>=、==、~= 关系运算符。
Lua 支持 and, or, not 逻辑运算符。
Lua 使用 .. 连接两个字符串。如果有一方为数字,则自动转换为字符串。将始终创建新字符串。
表构造表达式
构造函数是创建和初始化表的表达式。是 Lua 显著特征,最有用,最通用的机制之一。
空构造函数是最简单的。使用 {} 建立空表。
可以用任何类型表达式表示元素值。
使用 { key = value } 形式创建键值对。
可以在构造函数中使用分号分隔元素。 { one = 1; 'test' }
声明
Lua 允许多重赋值。a, b = 1, 2,但是 Lua 会首先计算所有值才进行赋值。
Lua 根据变量数量调整值数量,左侧变量比右侧值多时,多余的变量将赋值 nil。反之,多余的值会被丢弃。
variable = 10
local localVar = 20 -- 局部变量
局部变量仅作用于声明它的代码块。
使用 do end, 显示建立一个块。
--...
do
-- todo...
end
--...
控制语句
-- 使用 break 中断循环
-- 使用 return 中断执行并返回值
-- 因为语法原因。
-- break 或 return 只能作为块最后一个语句,或出现在 end, else, until 前。
-- if else
if true then print('true') end
if true then print('true') else print('false') end
if true then
-- ...
-- ...
elseif true then
-- ...
-- ...
else
-- ...
end
-- while
local i = 1
-- 如果 a[i] 存在 则执行
while a[i] do
print(a[i])
i = i + 1
end
-- repeat
-- 会重复执行直到条件为真
-- 至少会执行一次
repeat
-- ...
until true
-- for
-- 0 .. 10
for i = 0, 10, 1 do
-- ...
end
-- 10 .. 0
for i = 10, 0, -1 do
-- ...
end
-- 遍历数组
a = {1,2,3,4,5}
-- i 为索引
-- v 为值
for i, v in ipairs(a) do print(v) end
-- 遍历字典
for k, v in pairs(d) do print(k,v) end
函数
function computed (num1, num2)
return num1 + num2
end
Lua 为面向对象的调用提供了冒号运算符,O:fn(x) 等同于 O.fn(O, x)。
Lua 可以使用由 Lua 和 C 或其他主机应用使用的语言定义的函数。
function fn (n)
n = n or 1 -- 默认参数
return n,n+1,n+2 -- 返回多个值
end
a,b,c = fn() -- 获取多个返回值
-- 多余参数
function fn(a, b, ...)
-- fn(1,2,3,4)
-- a = 1
-- b = 2
-- arg = { 3, 4; n = 2 }
print(args)
end
-- 传入表作为命名参数使用
-- Lua 可以进行闭包。
function newCounter ()
local i = 0
return function ()
i = i + 1
return 1
end
end
-- Lua 函数可以存储在表或局部变量中
lib = {
fn = function () return 1 end
}
lib.fn()
迭代器 和 泛型
允许迭代集合元素的任何结构。
任何迭代器都需要再连续调用之间保留状态以便知道在何处并从何开始。
可以使用闭包。
每次循环迭代器都会新建一个闭包,可以使用泛型代替。
-- <exp> 是表达式列表 逗号分隔
-- <var> 是迭代的变量列表
for <exp> in <var> do
--
end
编译,执行,错误处理
Lua 提供 require 函数加载运行库。
require 在 路径中搜索文件,并控制文件是否运行以避免重复工作。是 Lua 加载库的首选。
Lua 使用的路径与其他不同,是一个模式列表,每个模式指定将虚拟文件转换为真实参数替代方法,即,路径中的每个组成部分都是包含可选标记的文件名。
对于每个组件,将检查是否存在该名称的文件,否则跳转到下一个组件。
路径的链接部分有分号分隔。
Lua 用 C 编写的包很容易,C 包在使用之前需要加载并与应用链接。通常使用动态链接工具。
Lua 提供 loadlib 函数,接收路径和初始化函数名称。
Lua 遇到的任何情况都会引发错误。使用 assets 断言可以进行检查,它会检查并返回第一个参数是否不为,假,为假时会报错。第二个参数则是可选的错误信息提示。
file = assert([[...]])
大多数程序不需要在 Lua 中处理错误。
如果需要,可以使用 pcall(protoecd all) 进行封装。
fn = function ()
-- 报错
print('a')
end
if pcall(fn)
协程
类似线程,一行执行,有自己的堆栈,局部变量和指令指针。
与线程不同,是协作的,具有协程的程序在任何给定时间仅运行一个协程。
Lua 提供了 coroutine,使用 coroutine.create 函数创建新的协程。
接收一个带有协程将运行代码的函数。返回一个 thread 值。
-- 通常参数为匿名参数
h = coroutine.create(function ()
-- ...
end
)
-- 使用 status 查询协程状态
-- 协程可以处于 挂起,运行,死亡三种状态其中之一
-- 创建协程时,默认挂起不执行
coroutine.status(h)
-- 使用 resume 重启运行协程
coroutine.resume(h)
-- 执行完毕后,协程死亡
-- 使用 yield 暂停协程执行,并在随后恢复
-- 协程内调用
coroutine.yield()
-- 使用 resume 重启运行协程恢复执行
coroutine.resume(h)
-- 协程传参
h = coroutine.create(function (a,b,c)
-- ...
return a + 1, b + 2, c + 3
end)
coroutine.resume(h, 1, 2, 3) -- 接收返回值 2, 4, 6
协程最典型的例子就是用于生产-消费问题。
当消费者需要内容时,启用生产者协程,直到获取到内容,然后停止,如此重复。
协程是非抢占式的。无法从外部停止,它只有得到明确请求时才停止。
Lua 数据结构
其他语言提供的结构,数组,字典,列表等。在 Lua 中的都可以用表有效的表示。
在 Lua 中,简单通过整数索引表实现数组,因此数组没有固定大小,而是随需求增长。
Lua 中有两个表示矩阵的主要方法,第一个是使用数组的数组,即二维数组。第二个方法则是将两个个索引合并为一个索引。
array = { 1, 2, 3, 4, 5, 6 }
因为 Lua 中字符串是不可变的,在某些情况下,如逐行读取文件就像 1+1=2,2+1=3,3+1=4,4+1=5…
因此,需要使用字符串缓冲。大致就是建立一个表而不是字符串用来存放每行的内容。然后读取结束后将表元素,也就是每一行的内容连接。
可以用 Lua 存放数据,需要数据时,直接执行 Lua 文件即可。
元表
Set = {}
Set.mt = {}
function Set.new(t)
local set = {}
-- 设置元表
setmetatable(set, Set.mt)
for _, l in ipairs(t) do set[l] = true end
return set
end
function Set.union(a, b)
local result = Set.new {}
-- 检查运算符
if getmetatable(a) ~= Set.mt or
getmetatable(b) ~= Set.mt
then
error('')
end
for k in pairs(a) do result[k] = true end
for k in pairs(b) do result[k] = true end
return result
end
function Set.intersection(a, b)
local result = Set.new {}
for k in pairs(a) do result[k] = b[k] end
return result
end
function Set.toString(set)
local str = '{'
local sep = ""
for char in pairs(set) do
str = str .. sep .. char
sep = ","
end
return str .. '}'
end
function Set.print(set)
print(Set.toString(set))
end
-- 加法
Set.mt.__add = Set.union
-- 乘法
Set.mt.__mul = Set.intersection
-- __sub 减法
-- __div 除法
-- __unm 求反
-- __pow 求幂
-- __eq 相等
-- __lt 小于
-- __le 小于等于
-- 大于等于 大于 不等于 没有单独元方法
-- 因为 Lua 将 a ~= b 视为 not ( a == b )
-- a > b --> b < a
-- a >= b --> b <= a
-- __tostring 转换字符串
-- __concat 自定义
-- __metatable 赋值将保护元表 无法观看修改
-- __index 访问缺失字段时,返回 nil,会触发, (table, value)
-- __newindex 分配不存在索引时触发调用,否则进行分配。(table, key, value)
S1 = Set.new { 1, 2, 3, 4, 5, 30, 50 }
S2 = Set.new { 30, 40, 50, 60, 6, 5 }
S3 = S1 + S2
Set.print(S3)
S3 = S1 * S2
Set.print(S3)
环境
Lua 将所有全局变量保存在一个名为 _G 的常规表全局变量中。
Lua 允许使用 _G[DynamicName] 的形式动态访问全局变量。
Lua 中的全局变量无需声明,但是较大程序中,可能会因为简单的拼写问题导致错误。而 Lua 的全局变量使用常规表保存,因此可以使用元表更改访问全局变量的行为。可以通过 rawset(_G, name, initval) 形式绕过访问控制。
非全局环境
Lua 允许使用 setfenv 设置函数环境。
包
Lua 没有为包提供明确机制,但可以用语言的基本机制实现。即,一个表表示一个包。
简单定义一个包的方法就是将包名称写为包内每个对象的前缀。如前面元表所举例的 Set。
此方法在调用包内方法时,必须添加前缀。
有时包会导出所有名称,即,该包的所有客户端都可以使用它们,但是,通常情况下,拥有私有名称是很重要的。
在 Lua 中,一个方便的方法就是将这些私有名称定义为局部变量。
使用 require 即可加载包。
使用独占环境,那么声明的全局变量都会自动进入该表。包需要做的就是将表名称注册为包名称。
文件 IO
Lua IO 库用于读取和处理文件,分为简单模式和完全模式。
- 简单模式:拥有一个当前输入和当前输出文件,并且针对这些文件提供操作。
- 完全模式:使用外部的文件句柄实现,以面向对象形式将所有文件操作定义为文件句柄。
简单模式用于简单的文件操作较为合适。但若是需要进行高级的文件操作,比如同时读取多个文件,那么还是使用完全模式较为合适。
-- 打开文件
file = io.open(filename [, mode])
mode 值如下:
- r 只读,文件必须存在。
- w 只写,文件存在会覆盖,否则建立。
- a 追加,文件不存在建立,否则追加。
- r+ 可读写,文件必须存在。
- w+ 可读写,文件存在则覆盖,否则建立。
- a+ 可读写追加。
- b 二进制。
- + 标识文件可读写。
简单模式
-- 只读打开文件
file = io.open(filename, 'r')
-- 设置默认输入文件
io.input(file)
-- 输入文件第一行
io.read()
-- 关闭打开文件
io.close(file)
-- 附加打开
file = io.open(filename, 'a')
-- 设置默认输出文件
io.output(file)
-- 写入文件
io.write('last')
-- 关闭文件
io.close(file)
完全模式
使用 file:[methods] 代替 io.[methods] 方法即可。
-- 只读打开
file = io.open(filename, 'r')
-- 输出
file:read()
-- 关闭
file:close()
错误处理
任何程序语言都有错误需要处理。
语法错误
通常由编程的语法问题,如运算符,表达式使用不当引起。
语法错误比运行错误更简单,运行错误一般无法定位具体错误,但是语法错误很快就能够进行处理。
运行错误
可使用 assert 和 error 两个函数处理错误。
-- assert 会检查第一个参数
-- 若参数不符合要求则会将第二个参数作为错误信息抛出
assert(a == 'b', 'a 不能等于 b')
-- 终止正在执行的函数
-- 并返回 message 内容作为错误信息
-- 通常情况下,error 会附加错误位置信息到 message 头部
-- level 指示获得错误位置
--- level 0 不添加错误位置信息
--- level 1 默认为调用 error 位置 (文件 + 行号)
--- level 2 指出那个调用 error 函数的函数
error(message [, level])
-- 使用 pcall 函数包装需要执行的代码
-- pcall 接受一个函数和传递给后者的参数并执行
-- 执行有错误返回 false 和错误信息
-- 否则仅返回 true
if pcall(fn) then
-- ...
end
-- pcall 以保护模式调用第一个参数 因此可以捕获函数执行中的错误
-- pcall 返回错误时,已经销毁调用栈部分
-- 因此 Lua 提供了 xpcall
-- xpcall 接受一个错误处理函数作为第二个参数
-- 可以在该函数内使用 debug 库获取错误额外信息
-- debug.debug 提供 lua 提示符 让用户检查错误原因
-- debug.traceback 根据调用栈构建扩展的错误信息
xpcall(
function (i) print(i) error ('err') end,
function () print(debug.traceback()) end, 3000)