Lua 语言基础
Lua
TOC:
- Lua 数据类型
- Lua 函数
- Lua 字符串
- Lua 数组
- Lua 迭代器
- Lua 表 table
- Lua 元表 Metatable
- Lua 模块与包
- Lua 协同程序 coroutine
- Lua I/O
- Lua 错误处理
- Lua 垃圾回收
应用:
基于 C 的脚本语言。
交互式
lua # or lua -i
注释
特例:shebang #~/usr/bin/env lua
单行注释:
-- 注释
多行注释:
--[[ 注释 [a]... 注释 --]]
标示符
定义变量,以字母 a-zA-Z
或下划线 _
开头,后接数字、字母、下划线组成,不允许使用特殊字符如 @, $, %
来定义标示符,区分大小写
Lua 关键字
| and | break | do | else | | elseif | end | false | for | | function | if | in | local | | nil | not | or | repeat | | return | then | true | until | | while | goto | | |
Lua 运算符
算术运算符:
- + 加法
- + 减法
- * 乘法
- / 除法
- % 取余
- ^ 乘幂
- - 负号
关系运算符:
- == 等于
- ~= 不等于
- >
- <
- >=
- <=
逻辑运算符:
- and 与
- or 或
- not 非
其他运算符:
- .. 连接两个字符串
- # 一元运算符,返回字符串或表的长度
运算符优先级
^ not - (unary) / * % + - .. < > <= >= ~= == and or
除了 ^
和 ..
外所有运算符都是左连接的
Lua 变量
lua 的变量默认都是全局变量,除非使用 local 进行声明
全局变量:默认情况下,变量总是认为是全局的,未初始化的全局变量的值为 nil
。
赋值:可对多个变量同时赋值,各元素用逗号 ,
分隔,若变量个数大于值的个数,补 nil,反之忽略多余的值
索引:使用 []
进行索引
Lua 语法结构
循环
- while
- for
- repeat until
循环控制语句
- break
- goto
while (true) do print("Hello") end goto Label ::Label::
for i=10, 1, -1 do print(i) end
流程控制
- if elseif else
if (f == 1) then code elseif then code else code end
Lua 数据类型
数据类型 | 说明 |
---|---|
nil | 标示无效值,逻辑判断为 false |
boolean | 布尔值,true / false |
number | 双精度实浮点数 |
string | 单引号或双引号包裹的字符串 |
function | 由 C 或 lua 编写的函数 |
userdata | 任意存储在变量中的 C 数据结构 |
thread | 标示执行的独立线程 |
table | 关联数组 associative arrays, {} |
Lua 函数
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn) function_body return result_params_comma_separated end
optionalfunctionscope: 指定全局还是局部,默认全局,使用 local 指定为局部
可变参数
在函数列表中使用三点 ...
表示函数有可变的参数,在函数中使用 {...}
表示由所有变长参数构成的数组,也可使用 select("#", ...)
来获取可变参数的数量, select(n, ...)
返回从起点 n 开始到结束位置的所有参数列表,固定参数必须放在变长参数之前。
Lua 字符串
表示方法:
- 单引号
''
- 双引号
""
- 双中括号
[[]]
转义字符同 C 语言
字符串方法
方法 | 含义 |
---|---|
string.upper(argument) | 将字符串全部转换为大写 |
string.lower(argument) | |
string.gsub(str, substr, replace, num) | 替换 |
string.find(str, substr[, init, [end]]) | 搜索 |
string.reverse(argument) | 反转 |
string.format(…) | 返回一个类似 printf 的格式化字符串 |
string.char(argument) | 将整型数字转为字符并连接 |
string.byte(argument[, int]) | 转换字符为整数值 |
string.len(str) | |
string.rep(str, n) | 返回 n 个 str 的拷贝 |
.. | 连接两个字符串 |
string.gmatch(str, pattern) | 返回一个迭代器函数,调用这个函数返回相应子串,无则 nil |
string.match(str, pattern, init) | 查找符合条件的第一个匹配 |
string.sub(str, init[, end]) | 截取 |
string.format(...)
函数类似 printf(...)
函数,转义码基本相同
匹配模式
单个字符与其自身匹配 (除 ^$()%*+-?
外)
字符类 | 含义 |
---|---|
. |
与任何字符匹配 |
%a |
字母 |
%c |
控制符 |
%d |
数字 |
%l |
小写字母 |
%p |
标点 punctuation |
%s |
空白字符 |
%u |
大写字母 |
%w |
字母/数字 |
%x |
十六进制 |
%z |
表零字符 |
%x |
转义 |
[] |
字符集 |
[^] |
非字符集 |
大写 z 及之前的字母表示 非
特殊字符
字符 | 含义 |
---|---|
* |
零或多个,贪婪匹配 |
+ |
一或多个 |
- |
零或多个,最短匹配 |
? |
零或一个 |
%n |
n = 1, 2, …, 9 表示第几个匹配 |
%bxy |
x, y 为字符,例如 %b() 表示括号平衡的表达式 |
%f[set] |
边境模式,匹配位于 set 内某个字符之前的空串 |
Lua 数组
相同数据类型按一定顺序排列的集合
一维数组
array = { "Lua", "Language" } for i = 0, 2 do print(array[i]) end
Lua 索引从 1 开始,但可以指定 0 开始
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组
Lua 迭代器
迭代器 iterator 是一种对象,能够用来遍历标准模板库容器中的部分或全部元素,每个迭代对象代表容器中的确定的地址,Lua 的迭代器是一种支持指针的结构。
泛型 for 迭代器
泛型 for 在内部保存迭代函数、状态常量、控制变量
array = {"a", "b"} for key, value in ipairs(array) do print(key, value) end
无状态的迭代器
指不保留任何状态的迭代器
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素,例如 ipairs()
,遍历数组的每一个元素,元素的索引需要是数值
🌰
function square(iteratorMaxCount, currentNumber) if currentNumber < iteratorMaxCount then currentNumber = currentNumber + 1 return currentNumber, currentNumber ^ 2 end end for i, n in square, 3, 0 do print(i, n) end
ipairs 实现:
function iter(a, i) i = i + 1 local v = a[i] if v then return i, v end end function ipairs(a) return iter, a, 0 end
当 Lua 调用 ipairs(a) 开始循环时,他获取三个值:迭代函数 iter、状态常量 a、控制变量初始值 0;然后 Lua 调用 iter(a, 0) 返回 1, a[1](除非 a[1]=nil);第二次迭代调用 iter(a,1) 返回 2, a[2]…… 直到遇到第一个 nil 元素。
多状态的迭代器
保存多个状态信息的迭代器,最简单方式是闭包,或者将所有的状态信息封装到 表 table 内,将 table 作为迭代器的状态常量
🌰
array = {"a", "b"} function elementIterator(collection) local index = 0 local count = #collection return function() if index <= count then return collection[index] end end end for element in elementIterator(array) do print(element) end
Lua 表 table
table 是关联型数组,可以使用任意类型的值(除 nil)作为数组的索引,同时 Lua 通过 table 来解决模块 (module), 包 (package) 和对象 (Object)的
通过 {}
来构造一个 table
-- 初始化一个 table mytable = {} -- 指定某个索引对应的值 mytable[1] = "Lua" -- 移除引用 mytable = nil -- lua 的 gc 会释放内存,引用计数
常用方法
方法 | 含义 |
---|---|
table.concat(table[, sep[, start[, end]]]) | concatenate,列出参数中指定的 table 的数组部分指定位置的所有元素,以指定的分隔符隔开 |
table.insert(table, [pos, ] value) | 在数组的指定位置插入值为 value 的元素,默认末尾 |
table.maxn(table) | 指定 table 中所有正数 key 值中最大的 key 值,lua 5.1 之前存在 |
table.remove(table[, pos]) | |
table.sort(table[, comp]) | 对 table 进行排序 |
function table_maxn(t) local mn = nil for k, v in pairs(t) do if mn == nil then mn = v end if mn < v then mn = v end end return mn end tbl = {[1] = 2, [2] = 6, [3] = 34, [26] = 5} print("tbl 最大值 ", table_maxn(tbl)) print("tbl 长度 ", #tbl) -- tbl 最大值 34 -- tbl 长度 3
Lua 模块与包
Lua 的模块是由变量、函数等已知元素组成的 table
require 函数
require("module") require "module"
加载机制
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUAPATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化
# LUA_PATH export LUA_PATH="~/lua/?.lua;;"
C 包
C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制
Lua 在一个叫 loadlib 的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数
local path = "/usr/local/lua/lib/libluasocket.so" local f = loadlib(path, "luaopen_socket")
loadlib 函数加载指定的库并且连接到 Lua,然而它并不调用初始化函数,返回初始化函数作为 Lua 的函数
local path = "/usr/local/lua/lib/libluasocket.so" local f = assert(loadlib(path, "luaopen_socket")) f()
Lua 元表 Metatable
允许改变 table 的行为,每个行为关联了对应的元方法
“重载”,例如 Lua 试图对两个表相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add
的字段,若找到,则调用对应的值。
相关函数:
setmetatable(table, metatable)
对指定表设置元表,如果元表metatable
中存在__metatable
键值,此函数会失败getmetatable(table)
返回 table 的元表
__index
元方法
定义了表在索引失败的情况下该怎么办
元表最常用的键,当通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的元表中的 __index
键,如果 __index
包含一个表格,Lua 会在表格中查找相应的键
如果 __index
包含一个函数的话,Lua 就会调用那个函数,table 和键会作为参数传递给函数
__index
元方法查看表中元素是否存在,如果不存在,返回结果为 nil~;如果存在则由 ~__index
返回结果
🌰
mytable = setmetatable({key1 = "value1"}, { __index = function(mytable, key) if key == "key2" then return "metatablevalue" else return nil end end }) print(mytable.key1, mytable.key2) --[[ mytable 的 key2 实际并不存在 但 mytable 存在元表 如果元表定义了 __index 就会调用这个 __index 方法 若 __index 为 nil,即未定义 返回 nil 若 __index 是一个 table 则在元表的 table 查找该索引 若 __index 是一个 函数 则调用该函数并返回函数结果 可以把元表理解一个 table 的操作指南 ]] -- value1 metatablevalue mytable = setmetatable({key = "value1"}, {__index = {key2 = "metatablevalue"}})
__newindex
元方法
__newindex
用来对表更新,当给 table 一个缺少的索引赋值,解释器就会查找 __newindex
元方法:如果存在就调用这个函数而不进行赋值操作
🌰
mymetatable = {} mytable = setmetable({key1 = "value1"}, {__newindex = mymetatable}) print(mytable.key1) mytable.newkey = "newvalue" print(mytable.newkey, mymetable.newkey) mytable.key1 = "value11" print(mytable.key1, mymetatable.key1) -- value -- nil newvalue -- value11 nil
🌰
mytable = setmetable({key1 = "value1"}, { __newindex = function(mytable, key, value) rawset(mytable, key, "\""..value.."\"") end }) mytable.key1 = "newvalue" mytable.key2 = 4 print(mytable.key1, mytable.key2) -- newvalue "4"
__call
元方法
在 Lua 调用 table 的一个值时调用
__string
元方法
修改 table 的输出行为
其他元方法
__add + __sub - __mul * __div / __mod % __unm - __concat .. __eq == __lt < __le <=
Lua 协同程序 coroutine
Lua 协同程序 (coroutine) 与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西
线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行;在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起
协同程序有点类似同步的多线程
基本方法
方法 | 含义 |
---|---|
coroutine.create() | 创建并返回 coroutine,参数是一个函数 |
coroutine.resume() | 启动/继续 coroutine |
coroutine.yield() | 挂起 coroutine |
coroutine.status() | 查看 coroutine 状态,包含 dead, suspended, running |
coroutine.wrap() | 创建 coroutine,调用时进入 coroutine |
coroutine.running() | 返回正在运行的 coroutine |
resume 和 create 配合使用时就唤醒函数调用
🌰 - 生产者-消费者问题
local newProductor function productor() local i = 0 while true do i = i + 1 send(i) end end fuction consumer() while true do local i = receive() print(i) end end function receive() local status, value = coroutine.resume(newProductor) return value end function send(x) coroutine.yield(x) end newProductor = coroutine.create(productor) consumer()
Lua I/O
- 简单模式:拥有一个当前输入文件和当前输出文件,并且提供对这些文件的相关操作
- 完全模式:使用外部的文件句柄来实现,以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法
file = io.open(filename[, mode])
mode:
- r 只读,文件必须存在
- w 只写
- a 附加
- r+ 可读写,文件必须存在
- w+ 可读写
- a+ 可读写
- b 二进制
- + 可读写
简单模式
🌰
-- 以只读方式打开文件 file = io.open("test.lua", "r") -- 设置默认输入文件为 test.lua io.input(file) -- 输出文件第一行 print(io.read()) -- 关闭打开的文件 io.close(file) -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 设置默认输出文件为 test.lua io.output(file) -- 在文件最后一行添加 Lua 注释 io.write("-- test.lua 文件末尾注释") -- 关闭打开的文件 io.close(file)
read 函数的参数
"*n"
读取一个数字并返回"*a"
从当前位置读取整个文件"*l
读取下一行- number 返回一个指定字符个数的字符串,或在 EOF 时返回 nil
io.tmpfile() 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
io.type(file) 检测 obj 是否是一个可用的文件句柄
io.flush() 向文件写入缓冲中的所有数据
io.lines(optional file name) 返回一个迭代函数,每次调用获取文件中一行内容
完全模式
同一时间处理多个文件,使用 ./function_name 来代替 io.functionname 方法
-- 以只读方式打开文件 file = io.open("test.lua", "r") -- 输出文件第一行 print(file:read()) -- 关闭打开的文件 file:close() -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 在文件最后一行添加 Lua 注释 file:write("--test") -- 关闭打开的文件 file:close()
方法:
./seek(optional whence, optional offset) 设置和获取当前文件位置,成功则返回最终的文件位置,失败则返回 nil 加错误信息
whence =
- “set” 从文件头开始
- “cur” 从当前位置开始
- “end” 从文件尾开始
offset 默认为 0
./lines(optional file name)
Lua 错误处理
assert()
assert(argument, message)
assert 首先检查第一个参数,若没问题,assert 不做任何事情;否则,assert 以第二个参数作为错误信息抛出
error()
error(message[, level])
终止正在执行的函数,并返回 message 的内容作为错误信息
level 参数指示获得错误的位置
- 1 默认,为调用 error 的位置,文件 + 行号
- 2 指出那个调用 error 的函数
- 0 不添加错误位置信息
pcall, xpcall
pcall 接收一个函数和要传递给后者的参数,并执行,返回值 true / false, errorinfo
if pcall(function_name) then else end
xpcall 相比与 pcall 接收第二个参数:一个错误处理函数,当错误发生时,Lua 会在调用栈展开 (unwind) 前调用错误处理函数
debug 库
Lua 垃圾回收
Lua 运行了一个垃圾收集器来收集所有死对象,实现了一个增量标记-扫描收集器。使用两个标记来控制垃圾收集循环,单位都是百分数
- 垃圾收集器间歇率 200 表示 2 倍内存时开始新的循环
- 垃圾收集器步进倍率 200 表示以内存分配的 2 倍速工作
collectgarbage([opt [, arg]])
opt =
- “collect” 做一次完整的垃圾循环
- “count” 以 K 字节数为单位返回 Lua 使用的总内存数
- “restart” 重启垃圾收集器的自动运行
- “setpause” 将 arg 设为收集器的间歇率,返回间歇率的前值
- “step” 单步运行垃圾收集器
- “stop” 停止垃圾收集器的运行