为什么要学Lua呢,因为大学时玩了一款游戏叫饥荒,最近发现做脚本也要用到这门语言,于是就简单的学习一下,毕竟广大程序员的乐趣就是会敲各种语言的Hello World。
此篇参考链接:
http://www.runoob.com/lua/lua-tutorial.html
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。这句话言外之意就是“我就是个做脚本的,目的就是为了嵌入到应用程序中辅助扩展”。
Lua 特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
- 其它特性:
- 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
- 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
- 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
- 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
Lua 应用场景
- 游戏开发 http://love2d.org/
- 独立应用脚本(这个就是我的目的,做游戏脚本)
- Web 应用脚本
- 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
- 安全系统,如入侵检测系统
Lua 环境安装
Windows环境安装
google code下载,这个需要翻墙,也可以从其他地方下载。
安装完后是个黑框框,cmd命令模式执行,跟java环境配置调用命令大同小异。这个是用来运行最终文件的。
跟java一样要有IDE,类似于IDEA和myeclipse,这个在安装Lua的过程中会有一个叫SciTE的软件,这个就是Lua的编辑器。
Linux安装环境
如果你没有Linux运行环境,仅仅是用Lua这么小的东西,不想安装虚拟机又不想买服务器的话。可以尝试腾讯云的Cloud Studio,在线编辑、运行。
Cloud Studio是基于浏览器的集成式开发环境,支持绝大部分编程语言,包括 HTML5、PHP、Python、Java、Ruby、C/C++、.NET 等等,无需下载安装程序,一键切换开发环境。Cloud Studio提供了完整的 Linux 环境,并且支持自定义域名指向,动态计算资源调整,可以完成各种应用的开发编译与部署。
新建界面时这个样子的,发现上面没有Lua,要去下载Code Runner插件。创建一个无来源的Blank,进入选择运行环境(这里我选择Java),然后安装Lua环境(效果和Windows安装的一样)
在java的linux上安装lua环境
sudo apt-get install libreadline-dev #先安装lua环境需要的lib,不行的话换个环境试试 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 sudo make install #一定要sudo,不然可能会出现权限不允许的情况,因为你是coding,文件夹是root
写个Hello World,没问题
如果你既不想装windows的环境也不想装linux的环境,仅仅是为了学习或者看Lua基础代码的运行结果,可以用lua官网自带的demo测试代码页面
Lua 基本语法
交互式编程
输入lua,进入控制台交互界面
脚本式编程
直接在cmd中输入lua main.lua,java是需要编译后再用java命令调用class文件,而lua不需要编译可以直接调用
注释
单行注释
两个减号是单行注释:
--
多行注释
--[[ 多行注释 多行注释 --]]
标示符
Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。
最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。
关键词
变量
全局变量:
在默认情况下,变量总是认为是全局的。全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。如果你想删除一个全局变量,只需要将变量赋值为nil。
一句话总结:变量默认全局,为初始化为nil,删除全局变量赋值为nil(和C语言置为空指针类似)
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。局部变量的作用域为从声明位置开始到所在语句块结束。变量的默认值均为 nil。
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值。
x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
变量个数 > 值的个数 按变量个数补足nil
变量个数 < 值的个数 多余的值会被忽略
多值赋值经常用来交换变量,或将函数调用返回给变量(函数可以返回多个变量)
f()返回两个值,第一个赋给a,第二个赋给b。应该尽可能的使用局部变量,有两个好处:
- 局部变量可以避免命名冲突。
- 访问局部变量的速度比全局变量更快。
对 table 的索引使用方括号 []。Lua 也提供了 . 操作
--[[ 全局变量与局部变量 ]]-- local a ='喵' --局部变量作用于当前块(这里相当于脚本文件) do local b ='喵喵喵' --局部变量作用于当前块(do end块) end local f = function(param) print(a,param) c = function() --全局变量作用于全局 end end f('汪') print(a,b,c,param)
Lua 数据类型
Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
例:
nil
nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值,对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉(类似于C的空指针)
nil 作比较时应该加上双引号 ",因为type函数返回的是一个字符串
boolean
Lua把false和nil 看作是"假",其他的都为"真"
number
Lua默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义)
string
一对单引号、双引号或方括号 "[[]]" 来表示"一块"字符串
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
字符串连接使用的是两个点 ..
使用 # 来计算字符串的长度,放在字符串前面
table
table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据(相当于数组),直接初始化表
Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串(相当于map,叫做字典)。
a = {} -- 新建一个全局空表(这里用法可以理解为一个map) a["key1"] = "value1" -- key和value key = 10 -- 定义key变量 a[key] = 22 -- 将变量key作为键,22作为值 a[key] = a[key] + 11 -- 根据键修改值 --[[遍历table]]-- for k, v in pairs(a) do print(k .. " : " .. v) end
结果如下:
不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
local tbl = {"apple", "pear", "orange", "grape"} -- 这个table看起来像数组 for key, val in pairs(tbl) do -- 遍历它,key为table的下标 print("Key", key) end
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table项都是 nil。
function
在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里
function factorial1(n) -- 阶乘算法函数 if n == 0 then return 1 else return n * factorial1(n - 1) end end print(factorial1(5)) --打印factorial1函数返回的值 factorial2 = factorial1 -- 把函数factorial1作为参数赋给全局变量factorial2 print(factorial2(5)) -- 调用函数factorial2
function可以以匿名函数(anonymous function)的方式作为参数传递
function testFun(tab,fun) -- 遍历tab,并对每个key-val做fun函数处理 for k ,v in pairs(tab) do print(fun(k,v)); end end tab={key1="val1",key2="val2"}; -- 匿名函数作为参数 testFun(tab, --第一个参数为表 function(key,val) --匿名函数作为第二个参数 return key.."="..val; --匿名参数实现内容 end );
function调用方式
. 方式赋值函数human更像作为表的一个属性,这个属性是function,参数是需要自己传的。
: 方式声明的函数可以通过self.的方式调用表对象的其他参数。
-- 函数调用方式 human = {}; human.money = 1000; -- .方式 human.isRich1 = function(h) return h.money>500; end -- :方式,self就代表函数所属的table function human:isRich2() return self.money>500; end print(human.isRich1(human)); print(human:isRich2());
thread
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
Lua 流程控制
布尔表达式,除了nil和false为假其余全部为真。取反用not
if(布尔表达式 1) then --[ 在布尔表达式 1 为 true 时执行该语句块 --] elseif( 布尔表达式 2) then --[ 在布尔表达式 2 为 true 时执行该语句块 --] else --[ 如果以上布尔表达式都不为 true 则执行该语句块 --] end
Lua 循环
while
statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句。
while(condition) do statements end
repeat...until
在条件进行判断前循环体都会执行一次。如果条件判断语句(condition)为 false,循环会重新开始执行,直到条件判断语句(condition)为 true 才会停止执行。
repeat statements until( condition )
for
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
for var=exp1,exp2,exp3 do <执行体> end
泛型for
遍历数组,i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
--打印数组a的所有值 a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end
遍历字典,k是key,v是value。
-- 遍历字典t的所有值,key一定为string所以不用加引号,value不一定为string所以要加 t = {name="Young",age = 23,sex = "girl"} for k, v in pairs(t) do print(k, v, t[k]) end
循环实例
--[[循环]]-- local a = 0 -- while死循环,每0.5秒执行一次,不影响性能 while wait(0.5) do print(a) a =a+1 end print('print loop end') -- repeat循环 repeat print(a) a = a+1 until a>10 -- for循环三个参数分别为起止值,步长 for i=1,10,1 do print(i) end --遍历数组 local t = {"Monday", "Tuesday", "Wednesday","Thursday","Friday","Saturday","Sunday"} t[1]="啦啦啦" --更改数组第一位的值 print(t[1]) for i, v in ipairs(t) do print(i, v,t[i]) end -- 遍历字典(key=value) local t2 = {Monday=1, Tuesday=2, Wednesday=3,Thursday=4,Friday=5,Saturday=6,Sunday=7} t2["Monday"]="啦啦啦~" -- 修改key为Monday的键值对的值为啦啦啦~ print(t2['Monday']) -- 打印键为Monday的键值对的值 for i, v in pairs(t2) do print(i, v,t2[i]) end
Lua 函数
Lua 函数主要有两种用途:
- 1.完成指定的任务,这种情况下函数作为调用语句使用;
- 2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。
function_scope function function_name( argument1, argument2, argument3..., argumentn) function_body return result_params_comma_separated end
解析:
-
function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
-
function_name: 指定函数名称。
-
argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
-
function_body: 函数体,函数中需要执行的代码语句块。
-
result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
多返回值
Lua函数可以返回多个结果值,比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)。
s, e = string.find("www.runoob.com", "runoob") print(s, e)
方法内写法
function maximum (a) -- 返回table a的最大值和索引 local mi = 1 -- 最大值索引,初始为1 local m = a[mi] -- 最大值 for i,val in ipairs(a) do if val > m then mi = i m = val end end return m, mi -- 返回多个值的写法 end print(maximum({8,10,23,12,5}))
可变参数
Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。
function average(...) -- 取平均值 result = 0 -- 结果 local arg_tbl ={...} --> arg 为一个表,局部变量,将输入的过个参数封装为一个表 for i,v in ipairs(arg_tbl) do -- 遍历表 result = result + v -- 将value相加 end print("总共传入 " .. #arg .. " 个数") return result/#arg_tbl -- 将综合除以总数量 end print("平均值为",average(10,5,3,4,5,6))
- select('#', …) 返回可变参数的长度
- select(n, …) 用于访问 n 到 select('#',…) 的参数
运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:
-
算术运算符
-
关系运算符
-
逻辑运算符
-
其他运算符
运算符优先级
^ not - (unary) * / + - .. < > <= >= ~= == and or
字符串
字符串或串(String)是由数字、字母、下划线组成的一串字符。
Lua 语言中字符串可以使用以下三种方式来表示:
- 单引号间的一串字符。
- 双引号间的一串字符。
- [[和]]间的一串字符。
转义字符
字符串操作
string.upper(argument) -- 字符串全部转为大写字母 string.lower(argument) -- 字符串全部转为小写字母 string.gsub(mainString,findString,replaceString,num) -- 在字符串中替换,mainString为要替换的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换) string.find (str, substr, [init, [end]]) --在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil。 string.reverse(arg) -- 字符串反转 string.format(...) -- 返回一个类似printf的格式化字符串 string.char(arg) 和 string.byte(arg[,int]) -- char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。 string.len(arg) -- 计算字符串长度。 string.rep(string, n) -- 返回字符串string的n个拷贝 .. -- 链接两个字符串 string.gmatch(str, pattern) -- 回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。 string.match(str, pattern, init) -- string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。 在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。
string.format字符串格式化
pattern匹配模式
数组
数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
一维数组
一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用for循环出数组中的元素
array = {"Lua", "Tutorial"} for i= 0, 2 do print(array[i]) end
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组。以下是一个三行三列的阵列多维数组
-- 初始化数组 array = {} for i=1,3 do array[i] = {} for j=1,3 do array[i][j] = i*j end end -- 访问数组 for i=1,3 do for j=1,3 do print(array[i][j]) end end
Lua 迭代器
泛型 for 迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in pairs(t) do print(k, v) end
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,它遍历数组的每一个元素。
function square(iteratorMaxCount,currentNumber) if currentNumber<iteratorMaxCount then currentNumber = currentNumber+1 return currentNumber, currentNumber*currentNumber end end for i,n in square,3,0 do print(i,n) end
table表
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。
table操作
获取table长度,当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
可以使用以下方法来代替:
function table_leng(t) local leng=0 for k, v in pairs(t) do leng=leng+1 end return leng; end
模块与包
模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下:
-- 文件名为 module.lua -- 定义一个名为 module 的模块(其实就是一个全局变量) module = {} -- 定义一个常量 module.constant = "这是一个常量" -- 定义一个函数 function module.func1() io.write("这是一个公有函数! ") end local function func2() print("这是一个私有函数!") end function module.func3() func2() end return module -- 返回对象(把开始定义的那个全局变量在最末尾处返回)
require 函数
require("<模块名>") --写法一 require "<模块名>" --写法二
用法一,全局要用
-- main.lua 文件 -- module 模块为上文提到到 module.lua require("module") -- 在最上面声明,相当于java的import print(module.constant) -- module.lua文件中声明的全局变量名module.变量名 module.func3() -- module.lua文件中声明的全局变量名module.函数名
用法二,局部要用
-- main2.lua 文件 -- module 模块为上文提到到 module.lua -- 别名变量 m local m = require("module") -- 把require函数返回module.lua文件中返回的全局变量module并赋值给本地变量m print(m.constant) m.func3()
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:
#LUA_PATH export 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") 时就会尝试打开以下文件目录去搜索目标。
/Users/dengjoe/lua/module.lua; ./module.lua /usr/local/share/lua/5.1/module.lua /usr/local/share/lua/5.1/module/init.lua /usr/local/lib/lua/5.1/module.lua /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的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
local path = "/usr/local/lua/lib/libluasocket.so" local f = loadlib(path, "luaopen_socket")
loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。
如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so" -- 或者 path = "C:\windows\luasocket.dll",这是 Window 平台下 local f = assert(loadlib(path, "luaopen_socket")) f() -- 真正打开库
一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。
将stub文件所在的目录加入到LUA_PATH,这样设定后就可以使用require函数加载C库了。
#LUA_PATHexport LUA_PATH="~/lua/?.lua;;"
Lua 元表(Metatable)
table的元表提供了一种机制,支持类似操作符重载的行为。在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
有两个很重要的函数来处理元表:
- setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元表(metatable)。
_index元方法
这是 metatable 最常用的键。当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
$ lua> other = { foo = 3 } > t = setmetatable({}, { __index = other }) > t.foo 3 > t.bar nil
不是表
mytable = setmetatable({key1 = "value1"}, { __index = function(mytable, key) if key == "key2" then return "metatablevalue" else return nil end end }) print(mytable.key1,mytable.key2) value1 metatablevalue
__newindex 元方法
_newindex 元方法用来对表更新,__index则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
mymetatable = {} mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable }) print(mytable.key1) mytable.newkey = "新值2" print(mytable.newkey,mymetatable.newkey) mytable.key1 = "新值1" print(mytable.key1,mymetatable.key1) value1 nil 新值2 新值1 nil
_add元方法
f1 = {a = 1, b = 2} -- 表示一个分数 a/b. f2 = {a = 2, b = 3} metafraction = {} function metafraction.__add(f1, f2) local sum = {} sum.b = f1.b * f2.b sum.a = f1.a * f2.b + f2.a * f1.b return sum end setmetatable(f1, metafraction) setmetatable(f2, metafraction) s = f1 + f2 -- 调用在f1的元表上的__add(f1, f2) 方法
__index、__add等的值,被称为元方法。
-- table元方法的清单:
-- __add(a, b) for a + b
-- __sub(a, b) for a - b
-- __mul(a, b) for a * b
-- __div(a, b) for a / b
-- __mod(a, b) for a % b
-- __pow(a, b) for a ^ b
-- __unm(a) for -a
-- __concat(a, b) for a .. b
-- __len(a) for #a
-- __eq(a, b) for a == b
-- __lt(a, b) a < b
-- __le(a, b) for a <= b
-- __index(a, b) <fn or a table> for a.b
-- __newindex(a, b, c) for a.b = c
-- __call(a, ...) for a(...)
Lua 协同程序(coroutine)
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
-- coroutine_test.lua 文件 co = coroutine.create( function(i) print(i); end ) coroutine.resume(co, 1) -- 1 print(coroutine.status(co)) -- dead print("----------") co = coroutine.wrap( function(i) print(i); end ) co(1) print("----------") co2 = coroutine.create( function() for i=1,10 do print(i) if i == 3 then print(coroutine.status(co2)) --running print(coroutine.running()) --thread:XXXXXX end coroutine.yield() end end ) coroutine.resume(co2) --1 coroutine.resume(co2) --2 coroutine.resume(co2) --3 print(coroutine.status(co2)) -- suspended print(coroutine.running()) print("----------")
Lua 文件 I/O
Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
打开文件语句
file = io.open (filename [, mode])
mod值
简单模式
简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
以下为 file.lua 文件代码,操作的文件为test.lua(如果没有你需要创建该文件),代码如下:
-- 以只读方式打开文件 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参数
io 方法有:
-
io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
-
io.type(file): 检测obj是否一个可用的文件句柄
-
io.flush(): 向文件写入缓冲中的所有数据
-
io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件
完全模式
通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。以下实例演示了如何同时处理同一个文件:
-- 以只读方式打开文件 file = io.open("test.lua", "r") -- 输出文件第一行 print(file:read()) -- 关闭打开的文件 file:close() -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 在文件最后一行添加 Lua 注释 file:write("--test") -- 关闭打开的文件 file:close()
其他方法:
-
file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:
- "set": 从文件头开始
- "cur": 从当前位置开始[默认]
- "end": 从文件尾开始
- offset:默认为0
-
file:flush(): 向文件写入缓冲中的所有数据
-
io.lines(optional file name): 打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。
若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如
错误处理
错误有语法错误(编辑器不够强大不能像java一样检测语法)和运行时错误(运行时出错,如空指针异常等),
我们可以使用两个函数:assert 和 error 来处理错误。实例如下:
local function add(a,b) assert(type(a) == "number", "a 不是一个数字") assert(type(b) == "number", "b 不是一个数字") return a+b end add(10)
assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。
error函数
error (message [, level])
功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回),通常情况下,error会附加一些错误位置的信息到message头部。
Level参数指示获得错误的位置:
- Level=1[默认]:为调用error位置(文件+行号)
- Level=2:指出哪个调用error的函数的函数
- Level=0:不添加错误位置信息
pcall
pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。
if pcall(function_name, ….) then -- 没有错误 else -- 一些错误 end
xpcall
通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但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)
Lua 调试(Debug)
调试类型
- 命令行调试
- 图形界面调试
命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。
图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。
Lua 面向对象
-- Meta class Shape = {area = 0} -- 基础类方法 new function Shape:new (o,side) o = o or {} setmetatable(o, self) self.__index = self side = side or 0 self.area = side*side; return o end -- 基础类方法 printArea function Shape:printArea () print("面积为 ",self.area) end -- 创建对象 myshape = Shape:new(nil,10) myshape:printArea() Square = Shape:new() -- 派生类方法 new function Square:new (o,side) o = o or Shape:new(o,side) setmetatable(o, self) self.__index = self return o end -- 派生类方法 printArea function Square:printArea () print("正方形面积为 ",self.area) end -- 创建对象 mysquare = Square:new(nil,10) mysquare:printArea() Rectangle = Shape:new() -- 派生类方法 new function Rectangle:new (o,length,breadth) o = o or Shape:new(o) setmetatable(o, self) self.__index = self self.area = length * breadth return o end -- 派生类方法 printArea function Rectangle:printArea () print("矩形面积为 ",self.area) end -- 创建对象 myrectangle = Rectangle:new(nil,10,20) myrectangle:printArea()