[TOC]
Lua学习笔记(整理的runoob加自己的见解)
注意事项
1- Lua中变量没有预定义类型,每个变量可以包含任意类型的值,要用就直接赋值一种数据类型的值 2- nil类型就nil一个值,表示变量是否被赋值,变量赋值成nil也表示删除变量 3- 使用Type(xxx变量) 可以获取该变量的数据类型 4- number 所有数字,int float double 类型等都为number类型了 5- 字符串一旦赋值不能被修改,可以通过方法string.gsub()来修改,可以写成’xxas’单引号,但是建议用双引号”” 6- number类型可以和全是数字的字符串进行计算,字符串会进行类型转换 7- .. 连接符号 ,可以连接字符串类型,也可以连接整形的变量,但是如果直接使用真实的数字要在后面加个空格,因为系统会把 数字.. 看出2个浮点 如 1..2 (错误写法) 1 ..2 (正确写法) 8- 类型不同,比较判断也不会相等,如number类型的123不等于string类型的123 9- 计算运算符中取余可以和浮点数计算,可以精确到小数级别 10- 关系运算符中~= 表示不等于,类似其他语言如c的 != 11- 逻辑运算符 and ,or ,not 对应 && ,|| , ! 12- 表达式:a and b a为假则返回a 否则返回b, a or b a为真返回a否则返回b ,简单理解and就是先判断a a正确就继续判断b,如果b也正确返回btrue,则if(a and b) 为true ,这实际上也是&&的使用原理一样 ,如果a为假就是false直接返回a if(a and b) 就是false了 。 or 同理 || 13- 赋值方式一:多个变量同时赋值,多的变量(变量个数多于值个数)默认为nil,少的变量(变量个数少于值个数)不做处理,可以类型混搭 14- 局部变量 用local修饰声明 ,内存在栈上,在一个函数,代码块{ }内 函数,代码块结束,内存自动释放 15- 全局变量在堆上,可以随时创建,程序结束,内存自动释放 16- 控制语句上,有if for where 没有switch结构 17- 控制语句上,不需要写 () {} {指代 :then } 指代 : end 18- 循环结构 while do end ,里面不能写() 以及 ++ – += 类似的运算符 19- 循环结构 for 也不需要写() 也不在赋值除了对第一个赋值,其他都可以省略写默认写为 <= +xx 或 >= -xx 20- 函数的返回值和其他语言一样可以返回个值,变量。但是不同的是可以同时返回多个变量,进行多个赋值 21- table可以有数组的形式,字典的形式,数组字典同时混合的形式 22- print()打印会默认换行 23- #号数组是计算数组容器table的下标个数,lua的数组容器下标从1开始计算递增,字典不包括key元素 24- 有两种遍历结构pairs ,ipairs ,第一个遍历所有元素,第二个只遍历没有key类型的元素(不遍历字典),不能返回nil 25 26--------------------- 27作者:liaoshengg 28来源:CSDN 29原文:https://blog.csdn.net/liaoshengg/article/details/81299018 30版权声明:本文为博主原创文章,转载请附上博文链接!
安装
- Mac下安装
Mac下安装好brew之后,直接用brew安装即可
brew install lua
或者使用编译安装的方式
1curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz 2tar zxf lua-5.3.0.tar.gz 3cd lua-5.3.0 4make macosx test 5make install # 权限不足的时候需要用root执行
-
linux下安装
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz tar zxf lua-5.3.0.tar.gz cd lua-5.3.0 make linux test make install # 权限不足的时候需要用root执行
开启交互式命令行模式
Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用
脚本式编程
lua脚本的后缀名为.lua
1#!/usr/local/bin/lua 2 3print("Hello World!")
和shell脚本一样, 如果文件头指定lua解释器的路径后可以直接./文件名运行
否则就要用lua ./文件名 运行.
注释
- 单行注释为
-- - 多行注释为
--[[ 注释内容 --]]
标识符和变量
标识符
Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。
最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。
Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 Runoob 与 runoob 是两个不同的标示符。
变量
在默认情况下,变量总是认为是全局的,
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量在使用前,必须在代码中进行声明,变量的默认值均为 nil。
全局变量不需要单独声明,给一个变量赋值后即创建了这个全局变量
访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
如果你想删除一个全局变量,只需要将变量赋值为nil。
局部变量 用local修饰声明 ,内存在栈上,在一个函数,代码块{ }内 函数,代码块结束,内存自动释放
全局变量在堆上,可以随时创建,程序结束,内存自动释放
1testA = 1 --全局变量 2local testB = 2 --局部变量 3 4print(testA) --输出1 5print(testB) --输出2 6print(testNone) --输出nil(不存在的变量不会报错) 7
- 变量赋值
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
1a, b = 10, 2*x --效果和下面的一样 2a=10; b=2*x -- 效果和上面的一样, 注意一行声明两个变量时需要用;分割 3
多个变量同时赋值时,当变量个数和值的个数不一致时,Lua会一直以变量个数为基础将没有值的变量设置为nil
1a,b,c = 0,1 2print(a) 3print(b) 4print(c)
多值赋值经常用来交换变量,或将函数调用返回给变量:
1x, y = y, x -- swap 'x' for 'y' 2a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]' 3 4a, b = f() --f()返回两个值,第一个赋给a,第二个赋给b。 5
应该尽可能的使用局部变量,有两个好处:
-
- 避免命名冲突。
-
- 访问局部变量的速度比全局变量更快。
索引
对 table 的索引使用方括号 []。Lua 也提供了用 . 来操作。
1t[i] 2t.i -- 当索引为字符串类型时的一种简化写法 3gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
例如:
1> site = {} 2> site["key"] = "www.w3cschool.cc" 3> print(site["key"]) --使用[]来获取索引下的值 4www.w3cschool.cc 5> print(site.key) --使用.来获取索引下的值 6www.w3cschool.cc
lua 数据类型
Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table
数据类型
描述
nil
这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean
包含两个值:false和true。
number
表示双精度类型的实浮点数
string
字符串由一对双引号或单引号来表示
function
由 C 或 Lua 编写的函数
userdata
表示任意存储在变量中的C数据结构
thread
表示执行的独立线路,用于执行协同程序
table
Lua 中的表(table)其实是一个"关联数组"(associative arrays),<br/>数组的索引可以是数字或者是字符串。<br/>在 Lua 里,table 的创建是通过"构造表达式"来完成,<br/>最简单构造表达式是{},用来创建一个空表。
我们可以使用 type 函数测试给定变量或者值的类型:
1print(type("Hello world")) --> string 2print(type(10.4*3)) --> number 3print(type(print)) --> function 4print(type(type)) --> function 5print(type(true)) --> boolean 6print(type(nil)) --> nil 7print(type(type(X))) --> string
nil(无效值)
nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值:
print(a) --结果为nil
对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,执行下面代码就知:
1tab1 = { key1 = "val1", key2 = "val2", "val3" } 2for k, v in pairs(tab1) do 3 print(k .. " - " .. v) 4end 5 6tab1.key1 = nil 7for k, v in pairs(tab1) do 8 print(k .. " - " .. v) 9end
nil 作比较时应该加上双引号"":
type(X)==nil 结果为 false 的原因是因为 type(type(X))==string。
1> type(X) 2nil 3> type(X)==nil 4false 5> type(X)=="nil" 6true 7>
boolean(布尔)
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是"假",其他的都为"真":
1> print(type(true)) 2boolean 3> print(type(false)) 4boolean 5> print(type(nil)) 6nil 7> 8 9if false or nil then 10 print("至少有一个是 true") 11else 12 print("false 和 nil 都为 false!") 13end
number(数字)
Lua 默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),以下几种写法都被看作是 number 类型:
1> print(type(2)) 2number 3> print(type(2.2)) 4number 5> print(type(0.2)) 6number 7> print(type(2e+1)) 8number 9> print(type(0.2e-1)) 10number 11> print(type(7.8263692594256e-06)) 12number 13>
string(字符串)
lua字符串说明
字符串或串(String)是由数字、字母、下划线组成的一串字符。
Lua 语言中字符串可以使用以下三种方式来表示:
- 单引号间的一串字符。
- 双引号间的一串字符。
- [[和]]间的一串字符。
以上三种方式的字符串实例如下:
1string1 = "Lua" 2print("\"字符串 1 是\"",string1) 3string2 = 'runoob.com' 4print("字符串 2 是",string2) 5 6string3 = [["Lua 教程"]] 7print("字符串 3 是",string3)
字符串由一对双引号或单引号来表示。
1string1 = "this is string1" 2string2 = 'this is string2'
多行字符使用[[]]来做标识
1> html = [[ 2>> <html> 3>> <head></head> 4>> <body> 5>> <a href="http://www.runoob.com/">菜鸟教程</a> 6>> </body> 7>> </html> 8>> ]] 9> print(html) 10<html> 11<head></head> 12<body> 13 <a href="http://www.runoob.com/">菜鸟教程</a> 14</body> 15</html> 16>
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字:
1> print("2" + 6) 28.0 3> print("2" + "6") 48.0 5> print("2 + 6") 62 + 6 7> print("-2e2" * "6") 8-1200.0 9> print("error" + 1) 10stdin:1: attempt to perform arithmetic on a string value 11stack traceback: 12 stdin:1: in main chunk 13 [C]: in ? 14>
以上代码中"error" + 1执行报错了,字符串连接使用的是 .. ,如:
1> print("a" .. 'b') 2ab 3> print(157 .. 428) 4157428 5>
使用 # 来计算字符串的长度,放在字符串前面,如下实例:
1> len = "www.runoob.com" 2> print(#len) 314 4> print(#"www.runoob.com") 514 6>
字符串操作
Lua 提供了很多的方法来支持字符串的操作:
序号
方法 & 用途
1
string.upper(argument): </br> 字符串全部转为大写字母。
2
string.lower(argument): </br> 字符串全部转为小写字母。
3
string.gsub(mainString,findString,replaceString,num)</br>在字符串中替换,mainString为要替换的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换),如:</br>> string.gsub("aaaa","a","z",3);</br>zzza 3
4
string.find (str, substr, [init, [end]])</br>在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil。</br>> string.find("Hello Lua user", "Lua", 1)</br>7 9
5
string.reverse(arg)</br>字符串反转</br>> string.reverse("Lua")</br>auL
6
string.format(...)</br>返回一个类似printf的格式化字符串</br>> string.format("the value is:%d",4)</br>the value is:4
7
string.char(arg) 和 string.byte(arg[,int])</br>char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。</br>> string.char(97,98,99,100)</br>abcd</br>> string.byte("ABCD",4)</br>68</br>> string.byte("ABCD")</br>65</br>>
8
string.len(arg)</br>计算字符串长度。</br>string.len("abc")</br>3
9
string.rep(string, n)</br>返回字符串string的n个拷贝</br>>string.rep("abcd",2)</br>abcdabcd
10
..</br>链接两个字符串</br>> print("www.runoob.".."com")</br>www.runoob.com
11
string.gmatch(str, pattern)</br>回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。</br>> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end </br> Hello </br> Lua </br> user
12
string.match(str, pattern, init) </br> string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。 </br> 在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。</br> > string.match("I have 2 questions for you.", "%d+ %a+") </br> 2 questions </br> > string.format("%d, %q", string.match("I have 2 questions for you.", "(%d+) (%a+)")) </br> 2, "questions"
字符串格式化
Lua 提供了 string.format() 函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。
由于格式字符串的存在, 使得产生的长字符串可读性大大提高了。这个函数的格式很像 C 语言中的 printf()。
以下实例演示了如何对字符串进行格式化操作:
格式字符串可能包含以下的转义码:
- %c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
- %d, %i - 接受一个数字并将其转化为有符号的整数格式
- %o - 接受一个数字并将其转化为八进制数格式
- %u - 接受一个数字并将其转化为无符号整数格式
- %x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
- %X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
- %e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
- %E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
- %f - 接受一个数字并将其转化为浮点数格式
- %g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
- %q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
- %s - 接受一个字符串并按照给定的参数格式化该字符串
为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:
- (1) 符号: 一个+号表示其后的数字转义符将让正数显示正号. - 默认情况下只有负数显示符号.
- (2) 占位符: 一个0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.
- (3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.
- (4) 宽度数值
- (5) 小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.
格式化案例:
1string.format("%c", 83) 输出S 2string.format("%+d", 17.0) 输出+17 3string.format("%05d", 17) 输出00017 4string.format("%o", 17) 输出21 5string.format("%u", 3.14) 输出3 6string.format("%x", 13) 输出d 7string.format("%X", 13) 输出D 8string.format("%e", 1000) 输出1.000000e+03 9string.format("%E", 1000) 输出1.000000E+03 10string.format("%6.3f", 13) 输出13.000 11string.format("%q", "One\nTwo") 输出"One\ 12 Two" 13string.format("%s", "monkey") 输出monkey 14string.format("%10s", "monkey") 输出 monkey 15string.format("%5.3s", "monkey") 输出 mon
字符与整数相互转换
以下实例演示了字符与整数相互转换:
1-- 字符转换 2-- 转换第一个字符 3print(string.byte("Lua")) 4-- 转换第三个字符 5print(string.byte("Lua",3)) 6-- 转换末尾第一个字符 7print(string.byte("Lua",-1)) 8-- 第二个字符 9print(string.byte("Lua",2)) 10-- 转换末尾第二个字符 11print(string.byte("Lua",-2)) 12 13-- 整数 ASCII 码转换为字符 14print(string.char(97))
以上代码执行结果为:
176 297 397 4117 5117 6a
其他常用函数
以下实例演示了其他字符串操作,如计算字符串长度,字符串连接,字符串复制等:
1string1 = "www." 2string2 = "runoob" 3string3 = ".com" 4-- 使用 .. 进行字符串连接 5print("连接字符串",string1..string2..string3) 6 7-- 字符串长度 8print("字符串长度 ",string.len(string2)) 9 10-- 字符串复制 2 次 11repeatedString = string.rep(string2,2) 12print(repeatedString)
以上代码执行结果为:
1连接字符串 www.runoob.com 2字符串长度 6 3runoobrunoob
匹配模式
Lua 中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match。
你还可以在模式串中使用字符类。
字符类指可以匹配一个特定字符集合内任何字符的模式项。比如,字符类%d匹配任意数字。所以你可以使用模式串 '%d%d/%d%d/%d%d%d%d' 搜索 dd/mm/yyyy 格式的日期:
1s = "Deadline is 30/05/1999, firm" 2date = "%d%d/%d%d/%d%d%d%d" 3print(string.sub(s, string.find(s, date))) --> 30/05/1999
下面的表列出了Lua支持的所有字符类:
单个字符(除 ^$()%.[]*+-? 外): 与该字符自身配对
- .(点): 与任何字符配对
- %a: 与任何字母配对
- %c: 与任何控制符配对(例如\n)
- %d: 与任何数字配对
- %l: 与任何小写字母配对
- %p: 与任何标点(punctuation)配对
- %s: 与空白字符配对
- %u: 与任何大写字母配对
- %w: 与任何字母/数字配对
- %x: 与任何十六进制数配对
- %z: 与任何代表0的字符配对
- %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*+-?)的配对问题, 例如%%与%配对
- [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对
[^数个字符类]: 与任何不包含在[]中的字符类配对.例如[^%s]与任何非空白字符配对
当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对.例如,'%A'非字母的字符:
1> print(string.gsub("hello, up-down!", "%A", ".")) 2hello..up.down. 4
数字4不是字符串结果的一部分,他是gsub返回的第二个结果,代表发生替换的次数。
在模式匹配中有一些特殊字符,他们有特殊的意义,Lua中的特殊字符如下:
( ) . % + - * ? [ ^ $
'%' 用作特殊字符的转义字符,因此 '%.' 匹配点;'%%' 匹配字符 '%'。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。
模式条目可以是:
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 '*', 将匹配零或多个该类的字符。这个条目总是匹配尽可能长的串;
- 单个字符类跟一个 '+', 将匹配一或更多个该类的字符。这个条目总是匹配尽可能长的串;
- 单个字符类跟一个 '-', 将匹配零或更多个该类的字符。 和 '*' 不同,这个条目总是匹配尽可能短的串;
- 单个字符类跟一个 '?', 将匹配零或一个该类的字符。只要有可能,它会匹配一个;
- %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
- %bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
- %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\0' 一样。
模式:
1模式 指一个模式条目的序列。 在模式最前面加上符号 '^' 2 3将锚定从字符串的开始处做匹配。 在模式最后面加上符号 '$' 4 5将使匹配过程锚定到字符串的结尾。 如果 '^' 和 '$' 6 7出现在其它位置,它们均没有特殊含义,只表示自身。
捕获:
1模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 2 3当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 4 5捕获物以它们左括号的次序来编号。 例如,对于模式 "(a*(.)%w(%s*))" , 字符串中匹配到 "a*(.)%w(%s*)" 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 "." 匹配到的字符是 2 号捕获物, 匹配到 "%s*" 的那部分是 3 号。 6 7作为一个特例,空的捕获 () 将捕获到当前字符串的位置(它是一个数字)。 8 9例如,如果将模式 "()aa()" 作用到字符串 "flaaap" 上,将产生两个捕获物: 3 和 5 。
table(表)
table说明和用法
table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。
table(表)的构造
构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。
可以直接初始化数组:
1-- 初始化表 2mytable = {} 3 4-- 指定值 5mytable[1]= "Lua" 6 7--指定值,(下标为字符串时可以使用.来访问和设置) 8mytable.name="liuhao" 9 10-- 移除引用 11mytable = nil 12-- lua 垃圾回收会释放内存
在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
1-- 创建一个空的 table 2local tbl1 = {} 3 4-- 直接初始表 5local tbl2 = {"apple", "pear", "orange", "grape"}
Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
1a = {} 2a["key"] = "value" 3key = 10 4a[key] = 22 5a[key] = a[key] + 11 6for k, v in pairs(a) do 7 print(k .. " : " .. v) 8end
脚本执行结果为:
1key : value 210 : 33
不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
1local tbl = {"apple", "pear", "orange", "grape"} 2for key, val in pairs(tbl) do 3 print("Key", key) 4end
脚本执行结果为:
1Key 1 2Key 2 3Key 3 4Key 4
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。
1a3 = {} 2for i = 1, 10 do 3 a3[i] = i 4end 5a3["key"] = "val" 6print(a3["key"]) 7print(a3["none"])
脚本执行结果为:
1val 2nil
当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。
如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。
以下实例演示了以上的描述情况:
1-- 简单的 table 2mytable = {} 3print("mytable 的类型是 ",type(mytable)) 4 5mytable[1]= "Lua" 6mytable["wow"] = "修改前" 7print("mytable 索引为 1 的元素是 ", mytable[1]) 8print("mytable 索引为 wow 的元素是 ", mytable["wow"]) 9-- talbe的访问,如果下标是字符串的也可以用.来表是 10print("mytable 索引为 wow 的元素是 ", mytable.wow) 11 12-- alternatetable和mytable的是指同一个 table 13alternatetable = mytable 14 15print("alternatetable 索引为 1 的元素是 ", alternatetable[1]) 16print("mytable 索引为 wow 的元素是 ", alternatetable["wow"]) 17 18alternatetable["wow"] = "修改后" 19 20print("mytable 索引为 wow 的元素是 ", mytable["wow"]) 21 22-- 释放变量 23alternatetable = nil 24print("alternatetable 是 ", alternatetable) 25 26-- mytable 仍然可以访问 27print("mytable 索引为 wow 的元素是 ", mytable["wow"]) 28 29mytable = nil 30print("mytable 是 ", mytable)
以上代码执行结果为:
1mytable 的类型是 table 2mytable 索引为 1 的元素是 Lua 3mytable 索引为 wow 的元素是 修改前 4alternatetable 索引为 1 的元素是 Lua 5mytable 索引为 wow 的元素是 修改前 6mytable 索引为 wow 的元素是 修改后 7alternatetable 是 nil 8mytable 索引为 wow 的元素是 修改后 9mytable 是 nil
Table 操作
以下列出了 Table 操作常用的方法:
序号
方法 & 用途
1
table.concat (table [, sep [, start [, end]]]): </br> concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。
2
table.insert (table, [pos,] value): </br> 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
3
table.maxn (table) </br> 指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了)
4
table.remove (table [, pos]) </br> 返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。
5
table.sort (table [, comp]) </br> 对给定的table进行升序排序。
table操作的方法的实例:
Table 连接
我们可以使用 concat() 方法来连接两个 table:
1fruits = {"banana","orange","apple"} 2-- 返回 table 连接后的字符串 3print("连接后的字符串 ",table.concat(fruits)) 4 5-- 指定连接字符 6print("连接后的字符串 ",table.concat(fruits,", ")) 7 8-- 指定索引来连接 table 9print("连接后的字符串 ",table.concat(fruits,", ", 2,3)) 10 11 12执行以上代码输出结果为: 13 14连接后的字符串 bananaorangeapple 15连接后的字符串 banana, orange, apple 16连接后的字符串 orange, apple
插入和移除
以下实例演示了 table 的插入和移除操作:
1fruits = {"banana","orange","apple"} 2 3-- 在末尾插入 4table.insert(fruits,"mango") 5print("索引为 4 的元素为 ",fruits[4]) 6 7-- 在索引为 2 的键处插入 8table.insert(fruits,2,"grapes") 9print("索引为 2 的元素为 ",fruits[2]) 10 11print("最后一个元素为 ",fruits[5]) 12table.remove(fruits) 13print("移除后最后一个元素为 ",fruits[5]) 14 15执行以上代码输出结果为: 16 17索引为 4 的元素为 mango 18索引为 2 的元素为 grapes 19最后一个元素为 mango 20移除后最后一个元素为 nil
Table 排序
以下实例演示了 sort() 方法的使用,用于对 Table 进行排序:
1fruits = {"banana","orange","apple","grapes"} 2print("排序前") 3for k,v in ipairs(fruits) do 4 print(k,v) 5end 6 7table.sort(fruits) 8print("排序后") 9for k,v in ipairs(fruits) do 10 print(k,v) 11end 12 13执行以上代码输出结果为: 14 15排序前 161 banana 172 orange 183 apple 194 grapes 20排序后 211 apple 222 banana 233 grapes 244 orange
Table 最大值
table.maxn 在 Lua5.2 之后该方法已经不存在了,我们定义了 table_maxn 方法来实现。
以下实例演示了如何获取 table 中的最大值:
1function table_maxn(t) 2 local mn=nil; 3 for k, v in pairs(t) do 4 if(mn==nil) then 5 mn=v 6 end 7 if mn < v then 8 mn = v 9 end 10 end 11 return mn 12end 13tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5} 14print("tbl 最大值:", table_maxn(tbl)) 15print("tbl 长度 ", #tbl) 16 17执行以上代码输出结果为: 18 19tbl 最大值: 34 20tbl 长度 3
注意:
当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
可以使用以下方法来代替:
1function table_leng(t) 2 local leng=0 3 for k, v in pairs(t) do 4 leng=leng+1 5 end 6 return leng; 7end
lua数组
lua数组下表默认从1开始
数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。
Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
一维数组
一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用for循环出数组中的元素,如下实例:
1array = {"Lua", "Tutorial"} 2 3for i= 0, 2 do 4 print(array[i]) 5end
以上代码执行输出结果为:
1nil 2Lua 3Tutorial
可以使用整数索引来访问数组元素,如果知道的索引没有值则返回nil。
在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。
除此外我们还可以以负数为数组索引值:
1array = {} 2 3for i= -2, 2 do 4 array[i] = i *2 5end 6 7for i = -2,2 do 8 print(array[i]) 9end
以上代码执行输出结果为:
1-4 2-2 30 42 54
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组。
以下是一个三行三列的阵列多维数组:
1-- 初始化数组 2array = {} 3for i=1,3 do 4 array[i] = {} 5 for j=1,3 do 6 array[i][j] = i*j 7 end 8end 9 10-- 访问数组 11for i=1,3 do 12 for j=1,3 do 13 print(array[i][j]) 14 end 15end
以上代码执行输出结果为:
11 22 33 42 54 66 73 86 99
不同索引键的三行三列阵列多维数组:
1-- 初始化数组 2array = {} 3maxRows = 3 4maxColumns = 3 5for row=1,maxRows do 6 for col=1,maxColumns do 7 array[row*maxColumns +col] = row*col 8 end 9end 10 11-- 访问数组 12for row=1,maxRows do 13 for col=1,maxColumns do 14 print(array[row*maxColumns +col]) 15 end 16end
以上代码执行输出结果为:
11 22 33 42 54 66 73 86 99
以上的实例中,数组设定了指定的索引值,这样可以避免出现 nil 值,有利于节省内存空间。
function(函数)
Lua 函数主要有两种用途:
- 完成指定的任务,这种情况下函数作为调用语句使用;
- 计算并返回值,这种情况下函数作为赋值语句的表达式使用。
lua 函数特点:
- 多返回值即:可以return多个值
- 可以接受可变数目的参数,和 C 语言类似
- 作为参数传递给函数
- 固定参数和可变参数可以混编,但是固定参数需要再前面
Lua 编程语言函数定义格式如下:
1optional_function_scope function function_name( argument1, argument2, argument3..., argumentn) 2 function_body 3 return result_params_comma_separated 4end
- optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
- function_name: 指定函数名称。
- argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
- function_body: 函数体,函数中需要执行的代码语句块。
- result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:
1function factorial1(n) 2 if n == 0 then 3 return 1 4 else 5 return n * factorial1(n - 1) 6 end 7end 8print(factorial1(5)) 9factorial2 = factorial1 10print(factorial2(5))
脚本执行结果为:
1120 2120
匿名函数
function 可以以匿名函数(anonymous function)的方式通过参数传递:
1--创建函数 2function testFun(tab,fun) 3 for k ,v in pairs(tab) do 4 print(fun(k,v)); 5 end 6end 7 8--创建一个table 9tab={key1="val1",key2="val2"}; 10 11--将table和匿名函数当做参数传入到testFun()函数当中 12testFun(tab, 13function(key,val)--匿名函数 14 return key.."="..val; 15end 16);
脚本执行结果为:
1key1 = val1 2key2 = val2
多返回值
Lua函数可以返回多个结果值,比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)。
1> s, e = string.find("www.runoob.com", "runoob") 2> print(s, e) 35 10
Lua函数中,在return后列出要返回的值的列表即可返回多值,如:
1function maximum (a) 2 local mi = 1 -- 最大值索引 3 local m = a[mi] -- 最大值 4 for i,val in ipairs(a) do 5 if val > m then 6 mi = i 7 m = val 8 end 9 end 10 return m, mi 11end 12 13print(maximum({8,10,23,12,5}))
以上代码执行结果为:
23 3
可变参数
Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。
1function add(...) 2local s = 0 3 for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组 4 s = s + v 5 end 6 return s 7end 8print(add(3,4,5,6,7)) --->25
我们可以将可变参数赋值给一个变量。
例如,我们计算几个数的平均值:
1function average(...) 2 result = 0 3 local arg={...} --> arg 为一个表,局部变量 4 for i,v in ipairs(arg) do 5 result = result + v 6 end 7 print("总共传入 " .. #arg .. " 个数") 8 return result/#arg 9end 10 11print("平均值为",average(10,5,3,4,5,6))
以上代码执行结果为:
1总共传入 6 个数 2平均值为 5.5
我们也可以通过 select("#",...) 来获取可变参数的数量:
1function average(...) 2 result = 0 3 local arg={...} 4 for i,v in ipairs(arg) do 5 result = result + v 6 end 7 print("总共传入 " .. select("#",...) .. " 个数") 8 return result/select("#",...) 9end 10 11print("平均值为",average(10,5,3,4,5,6))
以上代码执行结果为:
1总共传入 6 个数 2平均值为 5.5
几个固定参数加上可变参数,固定参数必须放在变长参数之前:
1function fwrite(fmt, ...) ---> 固定的参数fmt 2 return io.write(string.format(fmt, ...)) 3end 4 5fwrite("runoob\n") --->fmt = "runoob", 没有变长参数。 6fwrite("%d%d\n", 1, 2) --->fmt = "%d%d", 变长参数为 1 和 2
输出结果为:
1runoob 212
通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …) 或者 select(n, …)
- select('#', …) 返回可变参数的长度
- select(n, …) 用于访问 n 到 select('#',…) 的参数
调用select时,必须传入一个固定实参selector(选择开关)和一系列变长参数。
如果selector为数字n,那么select返回它的第n个可变实参,否则只能为字符串"#",这样select会返回变长参数的总数。例子代码:
1do 2 function foo(...) 3 for i = 1, select('#', ...) do -->获取参数总数 4 local arg = select(i, ...); -->读取参数 5 print("arg", arg); 6 end 7 end 8 9 foo(1, 2, 3, 4); 10end
输出结果为:
1arg 1 2arg 2 3arg 3 4arg 4
thread(线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata(自定义类型)
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
lua循环
Lua 语言提供了以下几种循环处理方式:
循环类型
描述
while
在条件为 true 时,让程序重复地执行某些语句。<br/>执行语句前会先检查条件是否为 true。
for
重复执行指定语句,重复次数可在 for 语句中控制。
repeat...until
重复执行循环,直到 指定的条件为真时为止
嵌套循环
可以在循环内嵌套一个或多个循环语句,如:<br/>(while do ... end;for ... do ... end;repeat ... until;)
while循环
1a=10 2while( a < 20 ) 3do 4 print("a 的值为:", a) 5 a = a+1 6end
执行以上代码,输出结果如下:
1a 的值为: 10 2a 的值为: 11 3a 的值为: 12 4a 的值为: 13 5a 的值为: 14 6a 的值为: 15 7a 的值为: 16 8a 的值为: 17 9a 的值为: 18 10a 的值为: 19
for循环
Lua语言中 for语句有两大类:
- 数值for循环
- 泛型for循环
数值for循环语句:
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
1-- var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。 2for var=exp1,exp2,exp3 do 3 <执行体> 4end
实例
1for i=1,f(x) do 2 print(i) 3end 4 5for i=10,1,-1 do 6 print(i) 7end
or的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中。
验证如下:
1#!/usr/local/bin/lua 2function f(x) 3 print("function") 4 return x*2 5end 6for i=1,f(5) do print(i) 7end
以上实例输出结果为:
1function 21 32 43 54 65 76 87 98 109 1110
可以看到 函数f(x)只在循环开始前执行一次。
泛型for循环(类似PHP中的foreach,可以用来遍历table等类型)
泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。
Lua 编程语言中泛型 for 循环语法格式:
1--打印数组a的所有值 2a = {"one", "two", "three"} 3for i, v in ipairs(a) do 4 print(i, v) 5end
i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
循环数组 days:
1#!/usr/local/bin/lua 2days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} 3for i,v in ipairs(days) 4do 5 print(v) 6end
以上实例输出结果为:
1Sunday 2Monday 3Tuesday 4Wednesday 5Thursday 6Friday 7Saturday
repeat...until 循环
Lua 编程语言中 repeat...until 循环语法格式:
1repeat 2 statements 3until( condition )
实例
1--[ 变量定义 --] 2a = 10 3--[ 执行循环 --] 4repeat 5 print("a的值为:", a) 6 a = a + 1 7until( a > 15 )
执行以上代码,程序输出结果为:
1a的值为: 10 2a的值为: 11 3a的值为: 12 4a的值为: 13 5a的值为: 14 6a的值为: 15
嵌套循环
Lua 编程语言中 for 循环嵌套语法格式:
1for init,max/min value, increment 2do 3 for init,max/min value, increment 4 do 5 statements 6 end 7 statements 8end
Lua 编程语言中 while 循环嵌套语法格式:
1while(condition) 2do 3 while(condition) 4 do 5 statements 6 end 7 statements 8end
Lua 编程语言中 repeat...until 循环嵌套语法格式:
1repeat 2 statements 3 repeat 4 statements 5 until( condition ) 6until( condition )
除了以上同类型循环嵌套外,我们还可以使用不同的循环类型来嵌套,如 for 循环体中嵌套 while 循环。
1j =2 2for i=2,10 do 3 for j=2,(i/j) , 2 do 4 if(not(i%j)) 5 then 6 break 7 end 8 if(j > (i/j))then 9 print("i 的值为:",i) 10 end 11 end 12end
以上代码执行结果为:
1i 的值为: 8 2i 的值为: 9 3i 的值为: 10
循环控制语句
循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
Lua 支持break语句用以退出当前循环或语句,并开始脚本执行紧接着的语句。
1---以下实例执行 while 循环,在变量 a 小于 20 时输出 a 的值,并在 a 大于 15 时终止执行循环: 2 3--[ 定义变量 --] 4a = 10 5 6--[ while 循环 --] 7while( a < 20 ) 8do 9 print("a 的值为:", a) 10 a=a+1 11 if( a > 15) 12 then 13 --[ 使用 break 语句终止循环 --] 14 break 15 end 16end
以上代码执行结果如下:
1a 的值为: 10 2a 的值为: 11 3a 的值为: 12 4a 的值为: 13 5a 的值为: 14 6a 的值为: 15
无限循环
在循环体中如果条件永远为 true 循环语句就会永远执行下去,以下以 while 循环为例:
1while( true ) 2do 3 print("循环将永远执行下去") 4end
lua流程控制
控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。
要注意的是Lua中 0 为 true:
1--[ 0 为 true ] 2if(0) 3then 4 print("0 为 true") 5end
以上代码输出结果为:
0 为 true
Lua 提供了以下控制结构语句:
语句
描述
if 语句
if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
if...else 语句
if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。
if 嵌套语句
你可以在if 或 else if中使用一个或多个 if 或 else if 语句 。
if 语句
在布尔表达式为 true 时会if中的代码块会被执行,在布尔表达式为 false 时,紧跟在 if 语句 end 之后的代码会被执行。
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
1if(布尔表达式) 2then 3 --[ 在布尔表达式为 true 时执行的语句 --] 4end
以下实例用于判断变量 a 的值是否小于 20:
1--[ 定义变量 --] 2a = 10; 3 4--[ 使用 if 语句 --] 5if( a < 20 ) 6then 7 --[ if 条件为 true 时打印以下信息 --] 8 print("a 小于 20" ); 9end 10print("a 的值为:", a);
以上代码执行结果如下:
1a 小于 20 2a 的值为: 10
if...else 语句
Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。
1if(布尔表达式) 2then 3 --[ 布尔表达式为 true 时执行该语句块 --] 4else 5 --[ 布尔表达式为 false 时执行该语句块 --] 6end
在布尔表达式为 true 时会if中的代码块会被执行,在布尔表达式为 false 时,else 的代码块会被执行。
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
以下实例用于判断变量 a 的值:
1--[ 定义变量 --] 2a = 100; 3--[ 检查条件 --] 4if( a < 20 ) 5then 6 --[ if 条件为 true 时执行该语句块 --] 7 print("a 小于 20" ) 8else 9 --[ if 条件为 false 时执行该语句块 --] 10 print("a 大于 20" ) 11end 12print("a 的值为 :", a)
以上代码执行结果如下:
1a 大于 20 2a 的值为 : 100
Lua if 语句可以与 elseif...else 语句搭配使用, 在 if 条件表达式为 false 时执行 elseif...else 语句代码块,用于检测多个条件语句。
1if( 布尔表达式 1) 2then 3 --[ 在布尔表达式 1 为 true 时执行该语句块 --] 4 5elseif( 布尔表达式 2) 6then 7 --[ 在布尔表达式 2 为 true 时执行该语句块 --] 8 9elseif( 布尔表达式 3) 10then 11 --[ 在布尔表达式 3 为 true 时执行该语句块 --] 12else 13 --[ 如果以上布尔表达式都不为 true 则执行该语句块 --] 14end
以下实例对变量 a 的值进行判断:
1--[ 定义变量 --] 2a = 100 3 4--[ 检查布尔条件 --] 5if( a == 10 ) 6then 7 --[ 如果条件为 true 打印以下信息 --] 8 print("a 的值为 10" ) 9elseif( a == 20 ) 10then 11 --[ if else if 条件为 true 时打印以下信息 --] 12 print("a 的值为 20" ) 13elseif( a == 30 ) 14then 15 --[ if else if condition 条件为 true 时打印以下信息 --] 16 print("a 的值为 30" ) 17else 18 --[ 以上条件语句没有一个为 true 时打印以下信息 --] 19 print("没有匹配 a 的值" ) 20end 21print("a 的真实值为: ", a )
以上代码执行结果如下:
1没有匹配 a 的值 2a 的真实值为: 100
if 嵌套语句
Lua if 语句允许嵌套, 这就意味着你可以在一个 if 或 else if 语句中插入其他的 if 或 else if 语句。
1if( 布尔表达式 1) 2then 3 --[ 布尔表达式 1 为 true 时执行该语句块 --] 4 if(布尔表达式 2) 5 then 6 --[ 布尔表达式 2 为 true 时执行该语句块 --] 7 end 8end
你可以用同样的方式嵌套 else if...else 语句。
以下实例用于判断变量 a 和 b 的值:
1--[ 定义变量 --] 2a = 100; 3b = 200; 4 5--[ 检查条件 --] 6if( a == 100 ) 7then 8 --[ if 条件为 true 时执行以下 if 条件判断 --] 9 if( b == 200 ) 10 then 11 --[ if 条件为 true 时执行该语句块 --] 12 print("a 的值为 100 b 的值为 200" ); 13 end 14end 15print("a 的值为 :", a ); 16print("b 的值为 :", b );
以上代码执行结果如下:
1a 的值为 100 b 的值为 200 2a 的值为 : 100 3b 的值为 : 200
lua运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 其他运算符
算术运算符
下表列出了 Lua 语言中的常用算术运算符,设定 A 的值为10,B 的值为 20:
操作符
描述
实例
加法
A + B 输出结果 30
-
减法
A - B 输出结果 -10
*
乘法
A * B 输出结果 200
/
除法
B / A w输出结果 2
%
取余
B % A 输出结果 0
^
乘幂
A^2 输出结果 100
-
负号
-A 输出结果v -10
运算符使用案例:
1> a = 21 2> b = 10 3> c = a + b 4> print("Line 1 - c 的值为 ", c ) 5Line 1 - c 的值为 31 6> c = a - b 7> print("Line 2 - c 的值为 ", c ) 8Line 2 - c 的值为 11 9> c = a * b 10> print("Line 3 - c 的值为 ", c ) 11Line 3 - c 的值为 210 12> c = a / b 13> print("Line 4 - c 的值为 ", c ) 14Line 4 - c 的值为 2.1 15> c = a % b 16> print("Line 5 - c 的值为 ", c ) 17Line 5 - c 的值为 1 18> c = a^2 19> print("Line 6 - c 的值为 ", c ) 20Line 6 - c 的值为 441.0 21> c = -a 22> print("Line 7 - c 的值为 ", c ) 23Line 7 - c 的值为 -21 24>
关系运算符
下表列出了 Lua 语言中的常用关系运算符,设定 A 的值为10,B 的值为 20:
操作符
描述
实例
==
等于,检测两个值是否相等,相等返回 true,否则返回 false (A == B) 为 false。
~=
不等于,检测两个值是否相等,相等返回 false,否则返回 true (A ~= B) 为 true。
>
大于,如果左边的值大于右边的值,返回 true,否则返回 false (A > B) 为 false。
<
小于,如果左边的值大于右边的值,返回 false,否则返回 true (A < B) 为 true。
>=
大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false (A >= B) 返回 false。
<=
小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false (A <= B) 返回 true。
关系运算符使用案例:
1a = 21 2b = 10 3 4if( a == b ) 5then 6 print("Line 1 - a 等于 b" ) 7else 8 print("Line 1 - a 不等于 b" ) 9end 10 11if( a ~= b ) 12then 13 print("Line 2 - a 不等于 b" ) 14else 15 print("Line 2 - a 等于 b" ) 16end 17 18if ( a < b ) 19then 20 print("Line 3 - a 小于 b" ) 21else 22 print("Line 3 - a 大于等于 b" ) 23end 24 25if ( a > b ) 26then 27 print("Line 4 - a 大于 b" ) 28else 29 print("Line 5 - a 小于等于 b" ) 30end 31 32-- 修改 a 和 b 的值 33a = 5 34b = 20 35if ( a <= b ) 36then 37 print("Line 5 - a 小于等于 b" ) 38end 39 40if ( b >= a ) 41then 42 print("Line 6 - b 大于等于 a" ) 43end
以上程序执行结果为:
1Line 1 - a 不等于 b 2Line 2 - a 不等于 b 3Line 3 - a 大于等于 b 4Line 4 - a 大于 b 5Line 5 - a 小于等于 b 6Line 6 - b 大于等于 a
逻辑运算符
下表列出了 Lua 语言中的常用逻辑运算符,设定 A 的值为 true,B 的值为 false:
操作符
描述
实例
and
逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B。
(A and B) 为 false。
or
逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B。
(A or B) 为 true。
not
逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。
not(A and B) 为 true。
逻辑运算符使用案例:
1a = true 2b = true 3 4if ( a and b ) 5then 6 print("a and b - 条件为 true" ) 7end 8 9if ( a or b ) 10then 11 print("a or b - 条件为 true" ) 12end 13 14print("---------分割线---------" ) 15 16-- 修改 a 和 b 的值 17a = false 18b = true 19 20if ( a and b ) 21then 22 print("a and b - 条件为 true" ) 23else 24 print("a and b - 条件为 false" ) 25end 26 27if ( not( a and b) ) 28then 29 print("not( a and b) - 条件为 true" ) 30else 31 print("not( a and b) - 条件为 false" ) 32end
以上程序执行结果为:
1a and b - 条件为 true 2a or b - 条件为 true 3---------分割线--------- 4a and b - 条件为 false 5not( a and b) - 条件为 true
其他运算符
下表列出了 Lua 语言中的连接运算符与计算表或字符串长度的运算符:
操作符
描述
实例
..
连接两个字符串
a..b ,其中 a 为 "Hello " , b 为 "World", 输出结果为 "Hello World"。
一元运算符,返回字符串或表的长度。
#"Hello" 返回 5
其他运算符使用案例:
1a = "Hello " 2b = "World" 3 4print("连接字符串 a 和 b ", a..b ) 5 6print("b 字符串长度 ",#b ) 7 8print("字符串 Test 长度 ",#"Test" ) 9 10print("菜鸟教程网址长度 ",#"www.runoob.com" )
以上程序执行结果为:
1连接字符串 a 和 b Hello World 2b 字符串长度 5 3字符串 Test 长度 4 4菜鸟教程网址长度 14
运算符优先级
从高到低的顺序:
1^ 2not - (unary) 3* / 4+ - 5.. 6< > <= >= ~= == 7and 8or
除了^和..外所有的二元运算符都是左连接的。
1a+i < b/2+1 <--> (a+i) < ((b/2)+1) 25+x^2*8 <--> 5+((x^2)*8) 3a < y and y <= z <--> (a < y) and (y <= z) 4-x^2 <--> -(x^2) 5x^y^z <--> x^(y^z)
运算符优先级案例:
1a = 20 2b = 10 3c = 15 4d = 5 5 6e = (a + b) * c / d;-- ( 30 * 15 ) / 5 7print("(a + b) * c / d 运算值为 :",e ) 8 9e = ((a + b) * c) / d; -- (30 * 15 ) / 5 10print("((a + b) * c) / d 运算值为 :",e ) 11 12e = (a + b) * (c / d);-- (30) * (15/5) 13print("(a + b) * (c / d) 运算值为 :",e ) 14 15e = a + (b * c) / d; -- 20 + (150/5) 16print("a + (b * c) / d 运算值为 :",e )
以上程序执行结果为:
1(a + b) * c / d 运算值为 : 90.0 2((a + b) * c) / d 运算值为 : 90.0 3(a + b) * (c / d) 运算值为 : 90.0 4a + (b * c) / d 运算值为 : 50.0
Lua迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址
在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型 for 迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
1for k, v in pairs(t) do 2 print(k, v) 3end
上面代码中,k, v为变量列表;pairs(t)为表达式列表。
查看以下实例:
1array = {"Lua", "Tutorial"} 2 3for key,value in ipairs(array) 4do 5 print(key, value) 6end
以上代码执行输出结果为:
11 Lua 22 Tutorial
以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。
下面我们看看泛型 for 的执行过程:
- 首先,初始化,计算in后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是ipairs,它遍历数组的每一个元素
以下实例我们使用了一个简单的函数来实现迭代器,实现 数字 n 的平方:
1function square(iteratorMaxCount,currentNumber) 2 if currentNumber<iteratorMaxCount 3 then 4 currentNumber = currentNumber+1 5 return currentNumber, currentNumber*currentNumber 6 end 7end 8 9for i,n in square,3,0 10do 11 print(i,n) 12end
以上实例输出结果为:
11 1 22 4 33 9
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs和迭代函数都很简单,我们在Lua中可以这样实现:
1function iter (a, i) 2 i = i + 1 3 local v = a[i] 4 if v then 5 return i, v 6 end 7end 8 9function ipairs (a) 10 return iter, a, 0 11end
当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作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,
所以迭代函数通常不需要第二个参数。
以下实例我们创建了自己的迭代器
1array = {"Lua", "Tutorial"} 2 3function elementIterator (collection) 4 local index = 0 5 local count = #collection 6 -- 闭包函数 7 return function () 8 index = index + 1 9 if index <= count 10 then 11 -- 返回迭代器的当前元素 12 return collection[index] 13 end 14 end 15end 16 17for element in elementIterator(array) 18do 19 print(element) 20end
以上实例输出结果为:
1Lua 2Tutorial
以上实例中我们可以看到,elementIterator 内使用了闭包函数,实现计算集合大小并输出各个元素。
Lua 模块与包
模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,
可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
以下为创建自定义模块 module.lua,文件代码格式如下:
1-- 文件名为 module.lua 2-- 定义一个名为 module 的模块 3module = {} 4 5-- 定义一个常量 6module.constant = "这是一个常量" 7 8-- 定义一个函数 9function module.func1() 10 io.write("这是一个公有函数!\n") 11end 12 13local function func2() 14 print("这是一个私有函数!") 15end 16 17function module.func3() 18 func2() 19end 20 21return module
由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.
require函数
5.2 版本之后的require 不再定义全局变量,需要保存其返回值。
require "luasql.mysql"需要写成:luasql = require "luasql.mysql"
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
1require("<模块名>") 25.2以后要写作这样 3变量名 = require("<模块名>")
或者
1require "<模块名>" 2变量名 = require "<模块名>"
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
5.2 之后不会直接返回由模块常量或函数组成的 table,需要用变量接一下.
1-- test_module.lua 文件 2-- module 模块为上文提到到 module.lua 3require("module") 4 5print(module.constant) 6 7module.func3()
以上代码执行结果为:
1这是一个常量 2这是一个私有函数!
或者给加载的模块定义一个别名变量,方便调用:
1-- test_module2.lua 文件 2-- module 模块为上文提到到 module.lua 3-- 别名变量 m 4local m = require("module") 5 6print(m.constant) 7 8m.func3()
以上代码执行结果为:
1这是一个常量 2这是一个私有函数!
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:
1#LUA_PATH 2export LUA_PATH="~/lua/?.lua;;"
文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。
source ~/.profile
这时假设 package.path 的值是:
/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua
那么调用 require("module") 时就会尝试打开以下文件目录去搜索目标。
1/Users/dengjoe/lua/module.lua; 2./module.lua 3/usr/local/share/lua/5.1/module.lua 4/usr/local/share/lua/5.1/module/init.lua 5/usr/local/lib/lua/5.1/module.lua 6/usr/local/lib/lua/5.1/module/init.lua
如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。
搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。
搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它
C 包
Lua和C是很容易结合的,使用C为Lua写包。
与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
1local path = "/usr/local/lua/lib/libluasocket.so" 2local f = loadlib(path, "luaopen_socket")
loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。
如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
1local path = "/usr/local/lua/lib/libluasocket.so" 2-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下 3local f = assert(loadlib(path, "luaopen_socket")) 4f() -- 真正打开库
一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。
将stub文件所在的目录加入到LUA_PATH,这样设定后就可以使用require函数加载C库了。
Lua 元表(Metatable)
lua元表说明
在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。
当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。
有两个很重要的函数来处理元表:
- setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元表(metatable)。
Lua 协同程序(coroutine)
Lua 协同程序(coroutine)与线程比较类似:
拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同是非常强大的功能,但是用起来也很复杂。
线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
基本语法
方法
描述
coroutine.create()
创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用
coroutine.resume()
重启coroutine,和create配合使用
coroutine.yield()
挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果
coroutine.status()
查看coroutine的状态<br/>注:coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序
coroutine.wrap()
创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复
coroutine.running()
返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号
以下实例演示了以上各个方法的用法:
1-- coroutine_test.lua 文件 2 3--创建一个匿名函数的协程,赋值为co,该方式创建的协程返回的是一个coroutine,后面要用的时候,需要配合resume来唤醒协程做调用 4co = coroutine.create( 5 function(i) 6 print(i); 7 end 8) 9 10--唤醒协程 同时传入协程的匿名函数和要处理的参数 11coroutine.resume(co, 1) -- 1 12print(coroutine.status(co)) -- dead 13 14print("----------") 15 16--用另一种方式创建一个协程,该方式创建的协程返回的是一个函数,可以直接传入参数调用 17co = coroutine.wrap( 18 function(i) 19 print(i); 20 end 21) 22 23--调用上面声明的协程,直接传入参数处理 24co(1) 25 26print("----------") 27 28--创建协程 29co2 = coroutine.create( 30 function() 31 for i=1,10 do 32 print(i) 33 if i == 3 then 34 -- 打印当前协程的状态 35 print(coroutine.status(co2)) --running 36 -- 打印当前正在运行中的协程 37 print(coroutine.running()) --thread:XXXXXX 38 end 39 --将协程设置为挂起状态 40 coroutine.yield() 41 end 42 end 43) 44 45coroutine.resume(co2) --1 46coroutine.resume(co2) --2 47coroutine.resume(co2) --3 48 49print(coroutine.status(co2)) -- suspended 50print(coroutine.running()) 51 52print("----------")
以上实例执行输出结果为:
11 2dead 3---------- 41 5---------- 61 72 83 9running 10thread: 0x7fb801c05868 false 11suspended 12thread: 0x7fb801c04c88 true 13----------
coroutine.running就可以看出来,coroutine在底层实现就是一个线程。
当create一个coroutine的时候就是在新线程中注册了一个事件。
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件
接下来我们分析一个更详细的实例:
1function foo (a) 2 print("foo 函数输出", a) 3 return coroutine.yield(2 * a) -- 返回 2*a 的值 4end 5 6co = coroutine.create(function (a , b) 7 print("第一次协同程序执行输出", a, b) -- co-body 1 10 8 local r = foo(a + 1) 9 10 print("第二次协同程序执行输出", r) 11 local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入 12 13 print("第三次协同程序执行输出", r, s) 14 return b, "结束协同程序" -- b的值为第二次调用协同程序时传入 15end) 16 17print("main", coroutine.resume(co, 1, 10)) -- true, 4 18print("--分割线----") 19print("main", coroutine.resume(co, "r")) -- true 11 -9 20print("---分割线---") 21print("main", coroutine.resume(co, "x", "y")) -- true 10 end 22print("---分割线---") 23print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine 24print("---分割线---")
以上实例执行输出结果为:
1第一次协同程序执行输出 1 10 2foo 函数输出 2 3main true 4 4--分割线---- 5第二次协同程序执行输出 r 6main true 11 -9 7---分割线--- 8第三次协同程序执行输出 x y 9main true 10 结束协同程序 10---分割线--- 11main false cannot resume dead coroutine 12---分割线---
以上实例接下如下:
- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
- 协同程序运行;
- 运行到yield语句;
- yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
- 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
- yield返回;
- 协同程序继续运行;
- 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine
resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;
而yield则将内部的状态(数据)返回到主程中。
生产者-消费者问题
现在我就使用Lua的协同程序来完成生产者-消费者这一经典问题。
1local newProductor 2 3function productor() 4 local i = 0 5 while true do 6 i = i + 1 7 send(i) -- 将生产的物品发送给消费者 8 end 9end 10 11function consumer() 12 while true do 13 local i = receive() -- 从生产者那里得到物品 14 print(i) 15 end 16end 17 18function receive() 19 local status, value = coroutine.resume(newProductor) 20 return value 21end 22 23function send(x) 24 coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序 25end 26 27-- 启动程序 28newProductor = coroutine.create(productor) 29consumer()
以上实例执行输出结果为:
11 22 33 44 55 66 77 88 99 1010 1111 1212 1313 14……
Lua 错误处理
任何程序语言中,都需要错误处理。错误类型有:
- 语法错误
- 运行错误
语法错误
语法错误通常是由于对程序的组件(如运算符、表达式)使用不当引起的。一个简单的实例如下:
1-- test.lua 文件 2a == 2
以上代码执行结果为:
lua: test.lua:2: syntax error near '=='
正如你所看到的,以上出现了语法错误,一个 "=" 号跟两个 "=" 号是有区别的。一个 "=" 是赋值表达式两个 "=" 是比较运算。
另外一个实例:
1for a= 1,10 2 print(a) 3end
执行以上程序会出现如下错误:
lua: test2.lua:2: 'do' expected near 'print'
语法错误比程序运行错误更简单,运行错误无法定位具体错误,而语法错误我们可以很快的解决,如以上实例我们只要在for语句下添加 do 即可:
1for a= 1,10 2do 3 print(a) 4end
运行错误
运行错误是程序可以正常执行,但是会输出报错信息。如下实例由于参数输入错误,程序执行时报错:
1function add(a,b) 2 return a+b 3end 4 5add(10)
当我们编译运行以下代码时,编译是可以成功的,但在运行的时候会产生如下错误:
1lua: test2.lua:2: attempt to perform arithmetic on local 'b' (a nil value) 2stack traceback: 3 test2.lua:2: in function 'add' 4 test2.lua:5: in main chunk 5 [C]: ?
以上报错信息是由于程序缺少 b 参数引起的。
错误处理
我们在操作数据库的时候,如果出现异常只会返回一个nil
这时候我们就可以用assert(连接语句)来捕捉错误,但是这种错误的获取方式,虽然可以正常可以得到错误信息,但是前段用户也能看得到.
比如我在NGINX连接MySQL使用assert来获取错误:
conn = assert(env:connect("test","root","","127.0.0.1","3306"))
这样一旦连接MySQL出现问题,错误信息会顺着NGINX返回给前端.
我们可以使用两个函数:assert 和 error 来处理错误。实例如下:
1local function add(a,b) 2 assert(type(a) == "number", "a 不是一个数字") 3 assert(type(b) == "number", "b 不是一个数字") 4 return a+b 5end 6add(10)
执行以上程序会出现如下错误:
1lua: test.lua:3: b 不是一个数字 2stack traceback: 3 [C]: in function 'assert' 4 test.lua:3: in local 'add' 5 test.lua:6: in main chunk 6 [C]: in ?
实例中assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。
error函数
语法格式:
error (message [, level])
功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)
通常情况下,error会附加一些错误位置的信息到message头部。
Level参数指示获得错误的位置:
- Level=1[默认]:为调用error位置(文件+行号)
- Level=2:指出哪个调用error的函数的函数
- Level=0:不添加错误位置信息
pcall 和 xpcall、debug
Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。
语法格式如下
1if pcall(function_name, ….) then 2-- 没有错误 3else 4-- 一些错误 5end
简单实例:
1> pcall(function(i) print(i) end, 33) 233 3true 4 5> pcall(function(i) print(i) error('error..') end, 33) 633 7false stdin:1: error.. 8 9> function f() return false,2 end 10> if f() then print '1' else print '0' end 110
pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。
通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。
Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
-
debug.debug:提供一个Lua提示符,让用户来检查错误的原因
-
debug.traceback:根据调用桟来构建一个扩展的错误消息
=xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33) 33 stack traceback: stdin:1: in function stdin:1 [C]: in function 'error' stdin:1: in function stdin:1 [C]: in function 'xpcall' stdin:1: in main chunk [C]: in ? false nil
xpcall 使用实例 2:
1function myfunction () 2 n = n/nil 3end 4 5function myerrorhandler( err ) 6 print( "ERROR:", err ) 7end 8 9status = xpcall( myfunction, myerrorhandler ) 10print( status)
执行以上程序会出现如下错误:
1ERROR: test2.lua:2: attempt to perform arithmetic on global 'n' (a nil value) 2false
Lua 调试(Debug)
Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码。
Lua 中 debug 库包含以下函数:
序号
方法
用途
debug():
进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。 <br/> 输入一行仅包含 cont 的字符串将结束这个函数, 这样调用者就可以继续向下运行。
getfenv(object):
返回对象的环境变量。
gethook(optional thread):
返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数
getinfo ([thread,] f [, what]):
返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字 f 表示该函数。 <br/> 数字 f 表示运行在指定线程的调用栈对应层次上的函数: <br/> 0 层表示当前函数(getinfo 自身); <br/> 1 层表示调用 getinfo 的函数 (除非是尾调用,这种情况不计入栈);等等。 <br/> 如果 f 是一个比活动函数数量还大的数字, getinfo 返回 nil。
debug.getlocal ([thread,] f, local):
此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。 <br/> 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。
getmetatable(value):
把给定索引指向的值的元表压入堆栈。如果索引无效,或是这个值没有元表,函数将返回 0 并且不会向栈上压任何东西。
getregistry():
返回注册表表,这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。
getupvalue (f, up):
此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。 <br/> 以 '(' (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。
sethook ([thread,] hook, mask [, count]):
将一个函数作为钩子函数设入。 <br/> 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义: <br/> 'c': 每当 Lua 调用一个函数时,调用钩子; <br/> 'r': 每当 Lua 从一个函数内返回时,调用钩子; <br/> 'l': 每当 Lua 进入新的一行时,调用钩子。
setlocal ([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。 <br/> 否则,它返回调用栈的栈回溯信息。 <br/> 字符串可选项 message 被添加在栈回溯信息的开头。 <br/> 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。
上表列出了我们常用的调试函数,接下来我们可以看些简单的例子:
1function myfunction () 2 print(debug.traceback("Stack trace")) 3 print(debug.getinfo(1)) 4 print("Stack trace end") 5 return 10 6end 7myfunction () 8print(debug.getinfo(1))
执行以上代码输出结果为:
1Stack trace 2stack traceback: 3 test2.lua:2: in function 'myfunction' 4 test2.lua:8: in main chunk 5 [C]: ? 6table: 0054C6C8 7Stack trace end
在以实例中,我们使用到了 debug 库的 traceback 和 getinfo 函数, getinfo 函数用于返回函数信息的表。
另一个实例
我们经常需要调试函数的内的局部变量。我们可以使用 getupvalue 函数来设置这些局部变量。实例如下:
1function newCounter () 2 local n = 0 3 local k = 0 4 return function () 5 k = n 6 n = n + 1 7 return n 8 end 9end 10 11counter = newCounter () 12print(counter()) 13print(counter()) 14 15local i = 1 16 17repeat 18 name, val = debug.getupvalue(counter, i) 19 if name then 20 print ("index", i, name, "=", val) 21 if(name == "n") then 22 debug.setupvalue (counter,2,10) 23 end 24 i = i + 1 25 end -- if 26until not name 27 28print(counter())
执行以上代码输出结果为:
11 22 3index 1 k = 1 4index 2 n = 2 511
在以上实例中,计数器在每次调用时都会自增1。实例中我们使用了 getupvalue 函数查看局部变量的当前状态。
我们可以设置局部变量为新值。实例中,在设置前 n 的值为 2,使用 setupvalue 函数将其设置为 10。现在我们调用函数,执行后输出为 11 而不是 3。
调试类型
- 命令行调试
- 图形界面调试
命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。
图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。
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 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。
- collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。
- collectgarbage("step"): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
- collectgarbage("stop"): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
以下演示了一个简单的垃圾回收实例:
1mytable = {"apple", "orange", "banana"} 2 3print(collectgarbage("count")) 4 5mytable = nil 6 7print(collectgarbage("count")) 8 9print(collectgarbage("collect")) 10 11print(collectgarbage("count"))
执行以上程序,输出结果如下(注意内存使用的变化):
120.9560546875 220.9853515625 30 419.4111328125
Lua 面向对象
lua面向对象特征
- 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
Lua 中面向对象
我们知道,对象由属性和方法组成。LUA中最基本的结构是table,所以需要用table来描述对象的属性。
lua中的function可以用来表示方法。那么LUA中的类可以通过table + function模拟出来。
至于继承,可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。
Lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
1Account = {balance = 0} 2function Account.withdraw (v) 3 Account.balance = Account.balance - v 4end
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,下面我们可以这样调用:
Account.withdraw(100.00)
一个简单实例
以下简单的类包含了三个属性: area, length 和 breadth,printArea方法用于打印计算结果:
1-- Meta class 2Rectangle = {area = 0, length = 0, breadth = 0} 3 4-- 派生类的方法 new 5function Rectangle:new (o,length,breadth) 6 o = o or {} 7 setmetatable(o, self) 8 self.__index = self 9 self.length = length or 0 10 self.breadth = breadth or 0 11 self.area = length*breadth; 12 return o 13end 14 15-- 派生类的方法 printArea 16function Rectangle:printArea () 17 print("矩形面积为 ",self.area) 18end
对象创建步骤
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
1r = Rectangle:new(nil,10,20) 2
访问属性
我们可以使用点号(.)来访问类的属性:
print(r.length)
访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
内存在对象初始化时分配。
完整实例
以下我们演示了 Lua 面向对象的完整实例:
1-- Meta class 2Shape = {area = 0} 3 4-- 基础类方法 new 5function Shape:new (o,side) 6 o = o or {} 7 setmetatable(o, self) 8 self.__index = self 9 side = side or 0 10 self.area = side*side; 11 return o 12end 13 14-- 基础类方法 printArea 15function Shape:printArea () 16 print("面积为 ",self.area) 17end 18 19-- 创建对象 20myshape = Shape:new(nil,10) 21 22myshape:printArea()
执行以上程序,输出结果为:
面积为 100
Lua 继承
继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。
以下演示了一个简单的继承实例:
1-- Meta class 2Shape = {area = 0} 3-- 基础类方法 new 4function Shape:new (o,side) 5 o = o or {} 6 setmetatable(o, self) 7 self.__index = self 8 side = side or 0 9 self.area = side*side; 10 return o 11end 12-- 基础类方法 printArea 13function Shape:printArea () 14 print("面积为 ",self.area) 15end 16
接下来的实例,Square 对象继承了 Shape 类:
1Square = Shape:new() 2-- Derived class method new 3function Square:new (o,side) 4 o = o or Shape:new(o,side) 5 setmetatable(o, self) 6 self.__index = self 7 return o 8end
完整实例
以下实例我们继承了一个简单的类,来扩展派生类的方法,派生类中保留了继承类的成员变量和方法:
1 -- Meta class 2Shape = {area = 0} 3-- 基础类方法 new 4function Shape:new (o,side) 5 o = o or {} 6 setmetatable(o, self) 7 self.__index = self 8 side = side or 0 9 self.area = side*side; 10 return o 11end 12-- 基础类方法 printArea 13function Shape:printArea () 14 print("面积为 ",self.area) 15end 16 17-- 创建对象 18myshape = Shape:new(nil,10) 19myshape:printArea() 20 21Square = Shape:new() 22-- 派生类方法 new 23function Square:new (o,side) 24 o = o or Shape:new(o,side) 25 setmetatable(o, self) 26 self.__index = self 27 return o 28end 29 30-- 派生类方法 printArea 31function Square:printArea () 32 print("正方形面积为 ",self.area) 33end 34 35-- 创建对象 36mysquare = Square:new(nil,10) 37mysquare:printArea() 38 39Rectangle = Shape:new() 40-- 派生类方法 new 41function Rectangle:new (o,length,breadth) 42 o = o or Shape:new(o) 43 setmetatable(o, self) 44 self.__index = self 45 self.area = length * breadth 46 return o 47end 48 49-- 派生类方法 printArea 50function Rectangle:printArea () 51 print("矩形面积为 ",self.area) 52end 53 54-- 创建对象 55myrectangle = Rectangle:new(nil,10,20) 56myrectangle:printArea() 57
执行以上代码,输出结果为:
1面积为 100 2正方形面积为 100 3矩形面积为 200
函数重写
Lua 中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
1-- 派生类方法 printArea 2function Square:printArea () 3 print("正方形面积 ",self.area) 4end