218. lua
Lua语言入门指南
安装 Lua 环境
Linux 环境安装
Linux & Mac上安装 Lua 安装非常简单,只需要下载源码包并在终端解压编译即可,本文使用了5.3.0版本进行安装
1 | curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz |
Mac OS 环境安装
1 | curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz |
第一个Lua程序
在Cent OS 的 Linux环境下演示:
1 | echo 'print("Hello,Lua!")' > hello.lua |
交互式编程
Lua提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。
Lua 交互式编程模式可以通过命令 lua -i 或者 lua 来启用:
1 | lua -i |
在命令行中输入如下命令
1 | > print("Hello,Lua!") |
脚本式编程
将lua脚本保存到 .lua 的文件中,再使用 lua 执行该脚本文件。
1 | echo 'print("Nice to meet you!")' > nice.lua |
Lua 的注释
单行注释
2个减号 – 用以表明是单行注释
多行注释
1 | --[[ |
标示符
Lua 标示符用于定义一个变量。
标示符以一个字母A 到 Z 或 a 到 z 或下划线 _开头后加上0个或多个字母,下划线,数字(0到9)。
最好不用下划线加大写字母开头的标示符,因为Lua的保留字也是这样的形式。
Lua 不允许使用特殊字符如 @,$,和 % 来定义标示符。
Lua区分大小写。
Lua 关键字
Lua 关键字,不允许用户自定义为标示符。
and | break | do | else |
elseif | end | false | for |
function | if | in | local |
nil | not | or | repeat |
return | then | true | until |
while |
一般约定,以下划线开头连接遗传大写字母的名字(比如 _VERSION)是Lua内部全局变量的保留字。
1 | > print(_VERSION) |
全局变量
默认情况下,变量总是认为全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是空: nul。
1 | > print(a) |
如果想删除一个全局变量,将其赋值为nil即可。
1 | > a = nil |
Lua 数据类型
Lua 有8个基本类型:
数据类型 | 描述 |
---|---|
nil | 是一个空值 |
boolea | false、true |
number | 表示双精度的浮点数 |
string | 字符串,由一堆双引号或者单引号包围 |
function | 由C或者Lua编写的函数 |
userdate | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线程,用于执行协同程序 |
table | Lua中的表是一个关联数组,数组的索引可以是数字或者字符串。最简单的表是{},表示是一个空表 |
使用type()函数测试给定变量或值得数据类型
1 | > type("Hello World!") |
nil 类型
nil 表示是一个无效的值。例如输出一个没有赋值的全局变量,便会得到一个 nil 值。
对于全局变量、table, nil 还有一个作用,给全局变量或者table表里的变量赋值为nil时,等同于把他们删掉。
1 | > tab1 = { key1 = "val1", key2 = "val2", "val3" } -- 定义表 |
boolean 类型
boolean 类型只有两个可选值: true、false;
Lua 把false、nil看作是“假”,其他都是“真”。
1 | # 查看boolean.lua里面的脚本内容 |
number 类型
Lua 只有一种number 的双精度类型(默认类型可以修改luaconf.h里的定义)。
1 | print(type(2)) |
string 字符串
字符串由一堆双引号或单引号来表示。
1 | string1 = "this is string1" |
也可以用2个方括号“[[]]”来表示”一块” 字符串。
1 | html = [[ |
注意: 当尝试对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转换成一个数字。
1 | print("123.4" + 4) |
注意: 字符串连接符号为: ..
1 | print("a" .. 'b') |
注意: 使用 # 符号来计算字符串的长度,放在字符串的前面。
1 | > str = "Hello,girl!" |
table 表
在 Lua里, table的创建是通过“构造表达式”来完成的,最简单的构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表。
1 | > tab1 = {} |
Lua 中的表是一个关联数组,数组的索引可以是一个数字或者字符串。
1 | > a = {} |
注意: 和其他语言不一样,Lua表的默认初始索引值一般从1开始。
function 函数
在Lua中,函数被看作是“第一类值(first-CLass Value)”,函数可以存在变量里。使用 function 关键字定义。
创建一个文件,写入:
1 | echo '' > func.lua |
执行脚本文件:
1 | lua func.lua |
我们定义了一个函数,并调用它。
然后将函数factorial1赋值给另一个变量factorial2,再调用另外一个变量factorial2(函数)。
function 还可以以匿名函数的方式通过参数传递:
1 | -- function_test2.lua 脚本文件 |
执行脚本:
1 | lua function_test2.lua |
thread 线程
再Lua里,最主要的是协程(coroutine)。它跟线程差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其它协程共享全局变量和其它大部分东西。
Lua 变量
Lua变量有三种类型: 全局变量、局部变量、表中的域。
Lua中的变量全是全局变量,哪怕是语句块或是函数里,除非用local显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的值默认为nil。
1 | -- test.lua 文件脚本 |
执行lua test.lua 输出结果为:
1 | lua test.lua |
赋值语句
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号隔开,赋值语句右边的值会一次赋给左边的变量。
1 | a, n = 10, 2*6 |
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以可以这样交换变量的值:
1 | x, y = y, x -- 直接交换了x,y二者的值;和python一样 |
当变量个数和值个数不匹配时,Lua以变量个数为基础,值不够的默认为nil;值过多的忽略。
Lua支持函数返回多个值,可以采用多值赋值:
1 | > function multival() |
总结: 应该尽可能使用局部变量,有两个好处:
- 避免命名冲突
- 访问局部变量速度比全局变量更快
索引
对table的索引使用方括号 **[]**。Lua 也提供了 . 操作。
1 | t[i] |
1 | > arr = {} |
Lua 语言编程指南
Lua 循环
Lua 提供了以下几种循环处理方式:
循环类型 | 描述 |
---|---|
while循环 | 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 |
for循环 | 重复执行指定语句,重复次数可在 for 语句中控制 |
repeat…until | 重复执行循环,直到 指定的条件为真时为止 |
循环嵌套 | 可以在循环内嵌套一个或多个循环语句(while do … end;for … do … end;repeat … until;) |
while 循环
语法:
1 | while(condition) |
示例:
1 | a=10 |
for 循环
for循环分为两大类:
- 数值for循环
- 泛型for循环
数值for循环
语法格式:
1 | for var=exp1,exp2,exp3 do |
var 从 exp1 变化到 exp2,每次变化以exp3为步长递增var,并执行一次语句块。exp3可选,不指定则默认为1.
示例:
1 | > for i = 1, 10, 1 do |
1 | for i=1,f(x) do |
注意: for的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中
泛型for循环
泛型for循环通过一个迭代器函数来遍历所有的值,类似java中的foreach语句。
语法格式:
1 | a = {"one", "two", "three"} |
i 是数组索引值, v是对应索引的数组元素值。 ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。
示例:
1 | > arr = {"Lily", "Jim", "Lucy", "Kate"} |
repeat…until 循环
repeat…until条件语句在当前循环结束后进行判断。
语法格式:
1 | repeat |
示例:
1 | a = 10 |
输出a的值,直到a的值大于15的时候,运行结果如下:
1 | > a = 10 |
Lua 流程控制语句
注意:Lua中除了false、nil为“假”之外其他所有值全部为“真”,0也是“真”。
语句 | 描述 |
---|---|
if语句 | if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 |
if…else语句 | if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码 |
if嵌套语句 | 可以在if 或 else if中使用一个或多个 if 或 else if 语句 。 |
if 语句
语法格式:
1 | if(布尔表达式) |
示例:
1 | a = 10 |
if…else 语句
语法格式:
1 | if (布尔表达式) |
示例:
1 | score = 90 |
if…elseif…else 语句
示例:
1 | --[ 定义变量 --] |
这是一个给考试分数判定级别的分支程序:
1 | > score = 85 |
if嵌套语句
语法格式类似:
1 | if( 布尔表达式 1) |
Lua 函数
函数定义语法格式:
1 | optional_function_scope function function_name( argument1, argument2, argument3..., argumentn) |
- optional_function_scope: 可选的指定函数是全局函数还是局部函数,默认为全局的; 局部使用 local 修饰。
- function_name: 指定函数名称。
- argument1, argument2, argument3…, argumentn:函数参数列表,逗号隔开,也可以不带参数。
- function_body: 函数体
- result_params_comma_separated:函数返回值,Lua语言函数可以返回多个值,用逗号隔开
示例:
1 | function max(num1, num2) |
Lua 中可以将函数作为参数传递给函数,示例:
1 | myprint = function(param) |
多返回值函数
Lua 函数可以返回多个值,比如 string.find,其返回匹配串“开始和结束的下标”(如果不存在匹配串则返回nil)
1 | > print(string.find("www.baidu.com", "baidu")) |
示例:
1 | function maximum (a) |
可变参数函数
Lua 函数支持可变参数。 和C语言类似,在参数列表中使用三点符 … 表示函数有可变的参数
示例:
1 | function add(...) --可变参数 |
select(“#”, …) 表达式来获取可变参数的个数
1 | function average(...) |
注意: 有时候需要几个固定参数 + 可变参数, 固定参数必须放在可变参数之前。
1 | function fwrite(fmt, ...) |
- select(“#”, …) : 返回可变参数的长度
- select(n, …) : 用于访问 n 到 select(“#”, …) 的参数
1 | function foo(...) |
Lua 运算符
Lua 提供了以下几种运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 其他运算符
算术运算符
+、-、*、/、%、^(乘幂,例如 2^2 值为 4)、-(负号)。
关系运算符
操作符 | 描述 | 示例 |
---|---|---|
== | 等于 | 1==1 为true |
~= | 不等于 | 1~=1为false |
> | 大于 | 2>1为true |
< | 小于 | 1<2为true |
>= | 大于等于 | 1>=1为true |
<= | 小于等于 | 1<=2为true |
逻辑运算符
逻辑运算符共有三个,示例,假设A为true,B为false
- and : A and B 为false
- or : A or B 为true
- not : not(A and B) 为true
其他运算符
操作符 | 描述 | 示例 |
---|---|---|
.. | 连接两个字符串 | a = “Hello”, b = “World!”, a..b = “HelloWorld!” |
# | 返回字符串或表的长度 | #”Hello” 返回5 |
1 | > a = "Hello" |
运算符优先级:
除了^和..外所有的二元运算符都是左连接的。从高到低的顺序依次是:
1 | ^ |
Lua 字符串
Lua 中的字符串可以使用以下三种方式来表示:
- 单引号之间的一串字符
- 双引号之间的一串字符
- [[ 和 ]] 之间的一串字符
示例如下:
1 | > str = [["你好"]] |
字符串操作函数
字符串string提供了一系列静态方法
序号 | 函数 | 描述 |
---|---|---|
1 | string.upper(argument) | 字符串全部转换为大写字母 |
2 | string.lower(argument) | 字符串全部转换为小写 |
3 | string.gsub(main String, find String, replaceString, num) | 在字符串中替换,mianString为与奥替换的字符串,findString为被替换的字符,replaceString为要替换的字符,num替换次数(不指定则全部替换) |
4 | string.find(str, substr, [init,[end]]) | 在一个制定的目标字符串中搜索指定的内容(第三个参数为索引)。返回其具体位置,不存在则返回nil |
5 | string.reverse(arg) | 字符串反转 |
6 | string.format(…) | 返回一个类似print的格式化字符串 |
7 | string.char(arg)和string.byte(arg[,int]) | char将整型字转换成字符并连接,byte转换字符为整数值(可以指定某个字符,默认第一个字符) |
8 | string.length(arg) | 计算字符串长度 |
9 | string.rep(string, n) | 返回字符串string的n个拷贝 |
10 | .. | 连接两个字符 |
11 | string.gmatch(str, pattern) | 一个迭代器函数,每一次调用这个函数,返回一个在字符串str找到的下一个符合pattern描述的字串。如果参数pattern描述的字符串没有找到,迭代函数返回nil |
12 | string.match(str, pattern, init) | 只寻找源字串str中的第一个配对,参数init可选,指定搜寻过程的起点,默认为1.在成功配对时,函数将返回配对表达式中的所有捕获结果,如果没有设置捕获标记,则返回整个配对字符串,当没有成功的配对时,返回nil。 |
Luau数组
数组,是相同数据类型的元素按照一定顺序排列的集合,可以是一维数组和多维数组
Lua 数组的索引值可以使用整数表示,数组的大小不是固定的。
一维数组
是最简单的数组,逻辑是线性表。 一维数组可以用for循环出数组中的元素。 如下示例:
1 | array = {"Lua", "Hello"} |
在 Lua 中索引值是从 1开始的, 如果指定的索引没有值则返回nil。还可以是负值。
多维数组
1 | -- 初始化数组 |
迭代器
迭代器是一种对象,能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在Lua中迭代器一种支持指针类型的结构,可以遍历集合的每一个元素。
泛型 for 迭代器
泛型 for 在自己内部保存迭代器,实际上它保存三个值: 迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对, 语法格式如下:
1 | for k, v in ipairs(t) do |
示例:
1 | > arr = {"Lua", "Hello"} |
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态控制 和 控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态的迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素。
一个简单的迭代器实现数字n的平方。
1 | function square(iteratorMaxCount, currentNumber) |
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs和迭代函数都很简单,还可以这样实现:
1 | function iter (a, i) |
当Lua调用ipairs(a)开始循环的时候,它获取三个值:迭代函数iter、状态常量a、控制变量初始值0;
然后Lua调用iter(a,0)返回1,a1;
第二次迭代调用 iter(a, 1),返回2,a[2]……直到第一个nil元素。
多状态的迭代器
很多情况下,迭代器需要村村多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包。
还有一种方法是将所有的状态信息封装到table中,将table作为迭代器的状态常量,因为这种情况下将所有的信息存放在table内,
所以迭代函数通常不需要第二个参数。
1 | function iterator(collection) |
Lua 表 Tbale
table 是 Lua的一种数据结构,以帮助我们创建不同的数据类型,如:数组、字典等。
Lua table 使用关联数组,可以用任意类型的值作为数组的索引,只要索引值不是nil。
Lua table 是不固定大小的,剋根据自己需要进行扩充。
Lua 也是通过table来解决模块、包和对象的。
例如 string.format 表示使用”format”来索引table string。
table 的构造
构造器是创建和初始化表的表达式。最简单的构造是{},用以构建一个空表。
1 | myTb = {} |
Lua 模块与包
模块类似于一个封装库,从Lua5.1开始,Lua加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里。以API接口的形式放在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个table。
自定义一个模块,格式如下:
1 | -- 文件名为 module.lua |
func2 声明为程序块的局部变量,表示是一个私有函数,不能从外部直接访问。
模块引入函数 require
Lua 提供了一个名为 require 的函数来加载模块。要加载一个模块,只需要简单调用就可以了。例如:
1 | require("模块名") |
注意:执行 require 后会返回一个由模块常量或函数组成的table,并且还会定义一个包含该 table 的全局变量。
1 | --当前是 test_module.lua 文件 |
加载机制
对于自定义的模块,模块文件不是放在那个文件目录都可以的。
函数 require 有它自己的文件路径加载策略,会尝试从 Lua 文件 或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path
中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始化这个环境变量。
如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
Lua 元表
在 Lua table 中我们可以访问对应的 key 来得到 value 的值,但是却无法对两个 table 进行操作。
因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
例如: 使用 元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。
当 Lua 试图对两个表进行相加时,先检查两者之一是否有元素,之后检查是否有一个叫 “__add” 的字段,有则调用对应的值。
有2个很重要的函数来处理元素:
- setmetatable(table, metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元素(metatable)
示例:
1 | mytable = {} -- 普通表 |
以上代码也可以直接写成一行:
1 | mytable = setmetatable({}, {}) |
__index 元方法
__index 包含表table示例
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假设有metatable)中的__index
键。
如果 __index
包含一个表格,Lua 会在表格中查找相应的键。
1 | other = { foo = 3 } |
解析:通过键foo访问table,就寻找表t的元表的__index
键,存在 __index
且包含一个表格other,则在other表中查找对应的foo键,找到值为3.
__index 包含函数示例
如果 __index
包含一个函数的话,Lua 就会调用那个函数, table 和 “键”会作为参数传递给函数。__index
元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index
返回结果。
1 | mytable = setmetatable({key1 = "value1"}, { |
给表 mytable 亦即 {key1 = “value1”},设置了元方法 __index 指向一个函数,如果传入key为key2的话,返回value2,这样类似增加了 key2=value2,
使得 mytable = {key1 = “value1”, key2 = “value2”}
解析如下:
- mytable 赋值为 {key1=”value1”}
- mytable 设置了元素,元方法为 __index
- 在 mytable 中查找 key1,如果找到,返回该元素,找不到则继续
- 在 mytable 中查找 key2,如果找到,返回 metatablevalue,找不到则继续
- 判断表有没有
__index方法
,如果__index
方法是一个函数,则调用该函数 - 元方法中查看是否传入了 “key2” 键的参数(mytable.key2已设置),如果传入”key2” 参数返回”metatablevalue”,否则返回mytable对应的键值。
上述使用函数的可以改写为使用表table:
1 | mytable = setmetatable({key1="key1"}, __index = {key2="metatablevalue"}) |
总结
Lua 查找一个表元素时的规则,其实就是如下三个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续。
- 判断该表是否有元素,如果没有元素,返回nil,有元素则继续。
- 判断元素有没有 __index 方法, 如果 __index 方法为nil, 则返回nil;如果 __index 方法是一个表,则重复1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
__newindex 元方法
__newindex 元方法用来对表更新, __index 则用来对表访问。
当你给表的一个缺少的索引赋值,解释器就会查找 __newindex 元方法;如果存在则调用这个函数而不进行赋值操作。
示例:
1 | mymetatable = {} |
上述示例:给表设置了元方法 __newindex
,在对新索引键(newkey)赋值时(mytable.newkey=”新值2”),会调用元方法,而不进行赋值。
而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex
。
给表添加操作符
演示为2个表添加相加操作:
1 | -- 计算表中最大值, table.maxn 在 Lua5.2以上版本中已无法使用 |
__add 键包含在元素中,并进行相加操作。表中对应的操作列表如下:
模式 | 描述 |
---|---|
__add | 对应的运算符’+’ |
__sub | 对应的运算符’-‘ |
__mul | 对应的运算符’*’ |
__div | 对应的操作符’/‘ |
__mod | 对应运算符’%’ |
__concat | 对应连接符’…’ |
__eq | 对应’==’ |
__lt | 对应’<’ |
__le | 对应’<=’ |
__unm | 对应负号’-‘ |
__call 元方法
__call 元方法在Lua调用一个值时调用。
演示计算表中元素的和:
1 | function table_maxn(t) |
__tostring 元方法
__tostring 元方法用于修改表的输出行为。
1 | mytable = setmetatable({10, 20, 30}, { |
总结: 元表可以很好的简化代码功能,了解元表,可以写出更加简洁优秀的Lua代码。
Lua 编程高级
Lua 协同程序(coroutine)
什么是协同(coroutine)?
Lua 协同程序coroutine与线程比较类似:拥有独立的堆栈、独立的局部变量,独立的指令指针,同时又与其他协同程序共享全局变量和其他大部分资源。
协同是非常强大的功能,但是用起来也很复杂。
线程和协同程序的区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任意时刻只有一个协同程序在运行,并且这个正在运行的程序只有在摩纳哥却的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程类似协同程序。
语法
方法 | 描述 |
---|---|
coroutine.create() | 创建coroutine,参数是一个函数,当和resume配合使用的时候就唤醒函数调用 |
coroutine.resume() | 重启coroutine,和create配合使用 |
coroutine.yield() | 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用可以产生很多效果 |
coroutine.status() | 查看coroutine的状态:dead,suspend,running |
coroutine.wrap() | 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复 |
coroutine.sunning() | 返回正在跑的coroutine,一个coroutine就是一个线程,当使用sunning的时候,就是返回一个coroutine的线程号 |
示例
1 | -- coroutine_test.lua 文件 |
从coroutine.running()的输出可以看出,coroutine在底层实现就是一个线程。
当create一个coroutine的时候就是在新线程中注册了一个事件。
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
示例:
1 | function foo (a) |
- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false。
- 协同程序运行
- 运行到yield语句
- yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
- 第二次resume,再次唤醒协同程序;(注意:此处resume参数中,除了第一个参数,剩下的参数作为yield的参数)
- yield返回
- 协同程序继续运行
- 如果使用的协同程序继续运行完成后继续调用resume方法则输出:cannot resume dead coroutine
resume 和 yield 的配合强大之处在于, resume处于主程中,它将外部状态(数据)传入到协同程序内部;而 yield 则将内部的状态(数据)返回到主程中。
生产者-消费者问题
使用 Lua 协同程序来完成 生产者-消费者 模式。
1 | local new Productor |
Lua 文件 IO
Lua I/O 库用于读写文件。 分为简单模式(和C一样)、完全模式。
- 简单模式: 拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式: 使用外部的文件句柄来实现。他以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法。
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。
例如同时读取多个文件这样的操作,使用完全模式较为合适。
打开文件操作语句如下:
1 | file = io.open(filename [, mode]) |
mode 的值有:
模式 | 描述 |
---|---|
r | 以只读方式打开文件,该文件必须存在 |
w | 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件 |
a | 以附加的方式打开只写文件。若文件不存在则建立该文件,如果文件存在,则写入的数据会被追加到文件末尾,即文件原来的内容会被保留。(EOF保留) |
r+ | 以可读写方式打开文件,该文件必须存在 |
w+ | 打开可读写文件,若文件存在则清零,不存在则建立该文件 |
a+ | 与a类似,但此文件可读可写 |
b | 二进制模式,如果文件是二进制文件,可以加上b |
+ | 表示对文件可读也可写 |
简单模式
简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
创建一个 test.lua 文件。
1 | -- 以只读方式打开文件 |
输出:
```– test.lua 文件末尾注释`````
在以上示例使用了 io.”x” 方法,其中 io.read() 方法我们没有带参数,参数可以是下表中的一个:
模式 | 描述 |
---|---|
“n” | 读取一个数字并返回它。例如 file.read(“n”) |
“*a” | 从当前位置读取整个文件。例如 file.read(“*a”) |
“*l” | 默认。读取下一行,在文件末尾(EOF)处返回nil。例如: file.read(“*l”) |
number | 返回一个指定字符个数的字符串,或者在EOF时返回nil。例如: file.read(5) |
其他的IO方法有:
- io.tmpfile() : 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除。
- io.type(file) : 检测io是否一个可用的文件句柄
- io.flush() : 向文件写入缓冲中的所有数据
- io.lines(optional file name) : 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件。
完全模式
通常我们需要在同一时间处理多个文件。我们需要使用 file:funcname 来代替 io.funcname 方法。
1 | file = io.open("test.lua","r") |
其他方法:
file:seek(optional whence, optional offset) : 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。
参数whence的值可以是:- “set” : 从文件头开始
- “cur” : 从当前位置开始(默认)
- “end” : 从文件末尾开始
- “offset” : 默认为0
不带参数 file:seek() 则返回当前位置,file:seek(“set”)则定为到文件头,file:seek(“end”)则定为文件末尾并返回文件大小。
file:flush() : 向文件写入缓冲中的所有数据
io.lines(optional file name) : 打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件末尾时,将返回nil,并自动关闭文件。
若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如:
1 | for line in io.lines("test.lua") do |
以下示例使用了seek方法,定位到文件倒数第25个位置并使用read方法的*a参数,即从当前位置(倒数第25个位置)读取整个文件。
1 | file = io.open("test.lua","r") |
Lua 错误处理机制
程序运行中错误处理是很有必要的,在我们进行文件操作,数据转移以及web service 调用过程中都会出现不可预期的错误。
如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。
任何语言,都需要进行错误处理,错误类型分为:
- 语法错误
- 运行错误
语法错误
语法错误通常是由于对程序组件(如运算符、表达式、函数等)使用不当导致的,一个简单示例如下:
1 | a ==2 |
执行结果为:
1 | stdin:1: '=' expected near '==' |
另一个示例:
1 | for a= 1,10 |
语法错误比程序运行错误更简单,语法错误可以很快解决,上述示例提示在print之前需要加上do。
运行错误
运行错误是程序可以正常执行,但是会输出报错信息,如下参数输入错误,程序执行时报错:
1 | function add(a, b) |
错误处理
Lua 中可以使用两个函数 : assert 和 error 来处理错误,如下:
1 | function add(a, b) |
示例中 assert 首先检查第一个参数,没有问题则不做任何事情;
否则检查第二个参数,错误则输出错误信息。
error 函数
语法格式:
1 | error (message [, level]) |
功能: 终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)
通常情况下,error会附加一些错误位置的信息到message头部。
level参数指示获得错误的位置:
- level = 1(默认):为调用error位置(文件+行号)
- level = 2:指出哪个调用error的函数的函数
- leve = 0:不添加错误位置信息
1 | > function add(a, b) |
pcall 和 xpcall、debug
Lua 中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall 接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者false,errorinfo。
语法格式如下:
1 | if pcall(funcname, ...) then |
示例如下:
1 | > = pcall(function(i) |
pcall 以一种“保护模式”来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。
通常在错误发生时,希望罗德更多的调试信息,而不只是发生错误的位置,但 pcall 返回时,他已经销毁了调用栈的部分内容。
Lua提供了xpcall函数,xpcall接收第二个参数———— 一个错误处理函数,当错误发生的时候,Lua会在调用栈展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
- debug.debug : 提供一个Lua提示符,让用户来检查错误的原因
- debug.traceback: 根据调用栈来构建一个扩展的错误信息
1 | > =xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33) |
xpcall使用示例1:
1 | function myfunction () |
Lua 调试
Lua 提供了debug库用于提供创建我们自定义调试器的功能。Lua 本身并没有内置的调试器,但是很多开发者共享了它们的Lua调试器代码。
Lua 中debug库包含以下函数:
方法 | 描述 |
---|---|
debug() | 进入一个用户交互模式,运行用户输入的每个字符串。使用简单的命令以及其他调试设置,用户可以检阅全局变量和局部变量,改变变量的值,计算一些表达式,等等。输入一行仅包含cont的字符串将结束这个函数,这样调用者就可以继续向下执行 |
getfenv(object) | 返回对象的环境变量 |
gethook(optional thread) | 返回三个表示线程钩子设置的值:当前钩子函数、当前钩子掩码、当前钩子计数 |
getinfo([thread,] f [,what]) | 返回一个函数信息的表。可以直接提供该函数,也可以用一个数字f表示函数。数字f表示运行在指定线程的调用栈对应层次上的函数:0层表示当前函数(getinfo自身);1表示调用getinfo的函数(除非是尾调用,这种情况下不计入栈);等等。如果f是一个比活动函数数量还大的数字,getinfo返回nil |
debug.getlocal([thread,] f, local) | 此函数返回在栈的f层处函数的索引为local的局部变量的名字和值。这个函数不仅用于访问显示定义的局部变量,也包括形参、临时变量等。 |
getmetatable(value) | 把指定索引指向的值的元素压入堆栈。如果索引无效,或是这个值没有元素,函数将返回0并且不会向栈上压任何东西。 |
getregistry() | 返回注册表,这是一个预定义出来的表,可以用来保存任何C代码想保存的Lua值 |
getupvalue(f, up) | 此函数返回函数f的第up个上值得名字和值。如果该函数没有那个上值,返回nil。以’(‘开头得变量名表示没有名字得变量(去除了调试信息得代码块) |
sethook([thread,] hook, mask [, count]) | 将一个函数作为钩子函数设入。字符串mask以及数字count决定了钩子在何时调用。掩码是由下列字符组合成得字符串,每个字符有其含义: ‘c’ : 每当Lua调用一个函数是,调用钩子; ‘r’ : 每当Lua从一个函数内返回时,调用钩子; ‘l’ : 每当Lua进入新的一行时,调用钩子 |
seylocal([thread,] level, local, value) | 这个函数将value赋给栈上第level层函数的第local个局部变量。如果没有那个变量,函数返回nil。如果level越界,抛出一个错误。 |
setmetatable(value, table) | 将value的元素设为table(可以是nil)。返回value |
setupvalue(f, up, value) | 这个函数将value设为函数f的第up个上值,如果函数没有那个上值,返回nil |
traceback([thread,] [message [, level]]) | 如果message有,且不是字符串或nil,函数不做任何处理直接返回message。否则,它返回调用栈的栈回溯信息。字符串可选项message被添加在栈回溯信息的开头。数字可选项level指明从栈的哪一层开始回溯(默认为1,即调用traceback的那里) |
简单示例:
1 | function myfunction() |
Lua 垃圾回收
Lua 采用了自动内存管理。这意味着你不用操心新创建的对象需要的内存如何分配出来,也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
Lua 运行了一个垃圾收集器来收集所有死对象(即在Lua中不能再访问到的对象)来完成自动内存管理的工作。Lua中所有用到的内存,如:字符串、表、用户数据、函数、线程、内部结构等,都服从内存管理。
Lua 实现了一个增量标记-扫描收集器。它使用这两个数字来控制垃圾收集循环:垃圾收集器间歇率和垃圾收集器步进倍率。这两个数字都使用百分数为单位(例如:值100在内部表示1)。
垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。增大这个值会减少收集器的积极性。当这个值比100小的时候,收集器在开始新的循环前不会有等待。设置这个值为200就会让收集器等到内存使用量达到之前的两倍时才开始新的循环。
垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。不要把这个值设的小于100,那样的话收集器就工作的太慢以至于干不完一个循环。默认值是200,表示收集器以内存分配的“两倍”速工作。
如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。
垃圾回收器函数
Lua 提供了一下函数 collectgarbage([opt [, arg]]) 用来控制自动内存管理。
- collectgarbage(“collect”) : 做一次完整的垃圾收集循环。通过参数opt它提供了一组不同的功能。
- collectgarbage(“count”) : 以k字节数为单位返回 Lua 使用的总内存数。这个值有小数部分,所以只需要乘以1024就能得到Lua使用的准确字节数(除非溢出)
- collectgarbage(“restart”) : 重启垃圾收集器的自动运行。
- collectgarbage(“setpause”) : 将arg设为收集器的间歇率。返回间歇率的前一个值。
- collectgarbage(“step”) : 单步运行垃圾收集器。步长“大小”由arg控制。传入0时,收集器步进(不可分割)一步。传入非0值,收集器相当于Lua分配这些多(k字节)内存的工作。如果收集器结束一个循环将返回true。
- collectgarbage(“stop”) : 停止垃圾收集器的运行。在调用重启前,收集器只会因显式地调用运行。
示例:
1 | > mytable = {"apple", "orange", "banana"} |
Lua 面向对象
面向对象特征:
- 封装: 指能够把一个实体的信息、功能、响应都装入一个单独地对象中的特性。
- 继承: 继承的方法允许在不改动源程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态: 同意操作作用不同的对象,实现不同的行为。
- 抽象: 简化复杂的现实问题。
Lua 中面向对象
Lua 中function可以用来表示方法。那么Lua中的类可以通过table+function模拟出来。
至于继承可以通过metatable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)
1 | Account = {balance = 0} |
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,可以这样调用:
1 | Account.withdraw(100.00) |
一个简单示例
以下简单示例包含了三个属性: area、length和breadth, printArea用于打印计算结果:
1 | -- Meta Class |
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
1 | r = Rectangle:new(nil, 10, 20) |
访问属性
可以使用点符.来访问类的属性:
1 | print(r.length) |
访问成员函数
我们可以使用冒号:来访问类的成员函数:
1 | > r:printArea() |
Lua 继承实例
1 | -- Meta class |
函数重写
Lua中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
1 | -- 派生类方法 printArea |
Lua 数据库访问
Lua 操作数据库的操作库: LuaSQL。是开源的支持ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL。
LuaSQL 可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。
LuaRocks 安装方法:
1 | wget http://luarocks.org/releases/luarocks-2.2.1.tar.gz |
安装不同数据库驱动:
1 | luarocks install luasql-sqlite3 |
Lua 连接MySQL实力:
1 | require "luasql.mysql" |