Lua 语言基础

Lua

交互式

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

./flush()

./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” 停止垃圾收集器的运行