文章目录
- `Lua`
- `Lua`语言的起源
- `Lua` 特性
- `Lua下载`
- `Lua`语言语法
- `Lua`关键字
- `Lua`全局变量
- `Lua`数据类型
- `Lua`变量
- `Lua` 循环
- 循环控制语句
- 无限循环
- `Lua` 流程控制
- `Lua`运算符
- `Lua`字符串
- 字符串操作
- `Lua`迭代器
- 泛型 for 迭代器
- `Lua table`表
- `Lua` 模块与包
- require 函数
- 加载机制
- C 包
- `Lua` 元表(`Metatable`)
- `Lua` 协同程序(coroutine)
- 什么是协同(coroutine)?
- 生产者-消费者问题
- `Lua` 文件 I/O
- `Lua` 错误处理
- `Lua` 调试(Debug)
- `Lua` 垃圾回收
- 垃圾回收器函数
- `Lua` 面向对象
- `Lua` 继承
- 函数重写
- `Lua` 数据库访问
- 函数重写
- `Lua` 数据库访问
Lua
Lua
语言的起源
Lua
是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua
是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro
)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy
、Waldemar Celes
和Luiz Henrique de Figueiredo
Lua
特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展:
Lua
提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua
可以使用它们,就像是本来就内置的功能一样。 - 其它特性
- 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
- 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
- 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
- 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
Lua下载
Linux 系统上安装
Linux & Mac上安装 Lua
安装非常简单,只需要下载源码包并在终端解压编译即可,本文使用了5.3.0版本进行安装:
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
Mac OS X 系统上安装
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 macosx test
make install
Window 系统上安装 Lua
window下你可以使用一个叫"SciTE"的IDE
环境来执行lua
程序,下载地址为:
本站下载地址:
Github
下载地址:https://github.com/rjpcomputing/luaforwindows/releasesGoogle Code下载地址 : https://code.google.com/p/luaforwindows/downloads/list
双击安装后即可在该环境下编写 Lua
程序并运行。
Lua
语言语法
####Lua
语言注释
-- 这是单行注释
--[[
这是多行注释
]]--
Lua
标识符
Lua
标识符用于定义变量、函数来获取其他用户定义的项
- 标示符以一个字母 A 到 Z 或 a 到 z 或**下划线 **_ 开头后加上 0 个或多个字母,下划线,数字(0 到 9)
注:不能以数字开头
-
最好不要使用下划线加大写字母的标示符,因为
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 | goto |
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于Lua
内部全局变量
Lua
全局变量
在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
print(b)
a = 1
print(a)
如果想要删除一个全局变量,只需要将变量赋值为nil就可以了
a = 1
print(a)
a = nil
print(a)
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),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
a = nil
b = true
c = false
d = 1.0
e = 5
f = "I am here"
g = function()
print("hello")
end
h = {name="Mike", age="18"}
print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(type(e))
print(type(f))
print(type(g))
print(type(h))
nil(空)
nil类型表示一种没有任何有效值的数据类型,他只有一个值–nil,例如打印一个没有赋值的变量,就会输出一个nil值
对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉
tab1 = {name="Mike", age=18 }
for k,v in pairs(tab1) do
print(k.."-->"..v)
end
tab1 = nil
for k,v in pairs(tab1) do
print(k.."-->"..v)
end
nil做比较时需要带上引号
x = nil
print(type(x))
-- x是nil type(x)表示type(type(x))==string
print(type(x)==nil)
print(type(x)=="nil")
boolean(布尔)
boolean 类型只有两个可选值:true(真) 和 false(假)
Lua
把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true:
number(数字)
Lua
默认只有一种 number 类型 – double(双精度)类型(默认类型可以修改 luaconf.h
里的定义)
string(字符串)
字符串由一对双引号或单引号来表示。
string1 = "this is string1"
string2 = 'this is string2'
也可以用 2 个方括号 “[[]]” 来表示"一块"字符串
name = [[
mike live behind a tree
]]
注意(关于字符串拼接和+):
算术运算时的“+”
在对一个数字字符串上进行算术操作时,Lua
会尝试将这个数字字符串转成一个数字:
print(1+2)
print("1"+2)
print("2"+"3")
print(2+"3")
print("error"+1)
类似于JavaScript中的隐式转换
字符串的拼接
如果单纯的只是想做到字符串的拼接:
print('a'..'b')
字符串长度的测量
a = "I am coming"
print(#a)
table(表)
在 Lua
里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}
-- 索引是从1开始的,类似于Pascal语言
print(tbl2[0])
print(tbl2[1])
Lua
中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串(你可以把它看作是python中的字典)
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
print(k .. " : " .. v)
end
可以通过索引键找到相对应的值
table的长度
table 不会固定长度大小,有新数据添加时 table 长度会自动增长
没初始的 table 都是 nil
a = {}
for i = 1,10 do
a[i]=i+1
end
a[0]="value"
for key,value in pairs(a) do
print(key.."-->"..value)
end
注意:即使添加的新的索引值小,依旧会出现在table的后面(即从table后方拼接上去)
function(函数)
在 Lua
中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:
-- 递归调用
function factorial(n)
if n==1 then
return 1
else
return n*factorial(n-1)
end
end
print(factorial(5))
factorial1 = factorial
print(factorial1(6))
function也可以像JavaScript那样定义匿名函数
map = function()
print("我爱你")
print("中国")
end
map()
thread(线程)
在 Lua
里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata
(自定义类型)
userdata
是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct
和 指针)存储到Lua
变量中调用
Lua
变量
变量在使用前,需要在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua
变量有三种类型:全局变量、局部变量、表中的域。
Lua
中的变量全是全局变量,那怕是语句块或是函数里
除非用 local 显式声明为局部变量
使用局部变量的好处
应该尽可能的使用局部变量,有两个好处:
- 避免命名冲突
- 访问局部变量的速度比全局变量更快
局部变量的作用域为从声明位置开始到所在语句块结束
a = 5
local b = 6
print("current value of a is "..a.."and current value of b is "..b)
function add_cd()
print("current value of a is "..a.."and current value of b is "..b)
c = 7
local d = 8
print("current value of c is "..c.."and current value of d is "..d)
end
add_cd()
print("current value of a is "..a.."and current value of b is "..b)
print("current value of c is "..c.."and current value of d is "..d)
function change_ab()
print("current value of a is "..a.."and current value of b is "..b)
a = 6
local b =3
print("current value of a is "..a.."and current value of b is "..b)
end
change_ab()
print("current value of a is "..a.."and current value of b is "..b)
Lua
赋值语句
复制是改变一个变量的值和改变表域的最基本的方法
a = "hello world"
Lua
也可以类似python那样多个变量同时赋值
a, b = 10, 20
-- 这样可以很方便的完成斐波那契数列的推演
a, b = b, a+b
遇到赋值语句Lua
会先计算右边所有的值然后再执行赋值操作
当遇到赋值语句左右两边个数不一致时
a, b = 1, 2, 3
-- 结果是 a=1 b=2 多余的3将会被舍弃
a, b, c = 1, 2
-- 结果是 a=1 b=2 c=nil
多值赋值常用来交换变量或者函数调用返回
a = 10
b = 30
a, b = b, a
function wrap_number(x, y)
return y, x
end
c, d = wrap_number(a,b)
print("current value of a is "..a.."and current value of b is "..b)
print("current value of c is "..c.."and current value of d is "..d)
Lua
循环
很多情况下我们需要做一些有规律性的重复操作,因此在程序中就需要重复执行某些语句。
一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件。
循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。
循环语句是由循环体及循环的终止条件两部分组成
Lua
语言提供了以下几种循环处理方式:
循环类型 | 描述 |
---|---|
while 循环 | 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 |
for 循环 | 重复执行指定语句,重复次数可在 for 语句中控制。 |
repeat…until | 重复执行循环,直到 指定的条件为真时为止 |
循环嵌套 | 可以在循环内嵌套一个或多个循环语句(while do … end;for … do … end;repeat … until;) |
While循环
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDeOF8aD-1604401078611)(C:\Users\13412\Desktop\lua_while_loop.jpg)]
语法
Lua
编程语言中 while 循环语法:
while(condition)
do
statements
end
例子:
a = 0
while (a<20) do
print("a value is "..a)
a = a + 1
end
For循环
Lua
编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
Lua
编程语言中 for语句有两大类:
- 数值for循环
- 泛型for循环
数值for循环
Lua
编程语言中数值 for 循环语法格式:
for var=exp1,exp2,exp3 do
<执行体>
end
var 从 exp1
变化到 exp2
,每次变化以 exp3
为步长递增 var,并执行一次 “执行体”。exp3
是可选的,如果不指定,默认为1。
例子:
for i = 1,20,3 do
print("i value is "..i)
end
泛型for循环
泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach
语句。
Lua
编程语言中泛型 for 循环语法格式:
--打印数组a的所有值
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
i是数组索引值,v是对应索引的数组元素值
ipairs
是Lua
提供的一个迭代器函数,用来迭代数组。
repeat…until循环
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfziVWQu-1604401078617)(C:\Users\13412\Desktop\lua_repeat_until_loop.jpg)]
学过Pascal的小伙伴一定都是到repeat…until当假运行
语法
Lua
编程语言中 repeat…until 循环语法格式:
repeat
statements
until( condition )
例子:
a = 1
repeat
print("a value is "..a)
a = a+1;
until (a>10)
循环控制语句
循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
Lua
支持以下循环控制语句:
控制语句 | 描述 |
---|---|
break 语句 | 退出当前循环或语句,并开始脚本执行紧接着的语句。 |
goto 语句 | 将程序的控制点转移到一个标签处。 |
break语法
Lua
编程语言 break 语句插入在循环体中,用于退出当前循环或语句,并开始脚本执行紧接着的语句。
如果你使用循环嵌套,break语句将停止最内层循环的执行,并开始执行的外层的循环语句。
a = 1
repeat
print("a value is "..a)
a = a+1;
if (a==3) then
break
end
until (a>10)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfZGyDaW-1604401078620)(C:\Users\13412\Desktop\cpp_break_statement.jpg)]
goto语句
Lua
语言中的 goto 语句允许将控制流程无条件地转到被标记的语句处。
语法(lua5.1
版本一下不支持goto)
语法格式如下所示:
goto Label
Label 的格式为:
:: Label ::
使用goto实现continue
for i=1, 3 do
if i <= 2 then
print(i, "yes continue")
goto continue
end
print(i, " no continue")
::continue::
print([[i'm end]])
end
无限循环
在循环体中如果条件永远为 true 循环语句就会永远执行下去,以下以 while 循环为例:
while( true )
do
print("循环将永远执行下去")
end
这样会大量消耗计算机资源
Lua
流程控制
Lua
编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。
以下是典型的流程控制流程图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUsgrbD7-1604401078625)(C:\Users\13412\Desktop\if.png)]
a = 76
if (90<=a and a<=100) then
print("you are excellent")
else
print("you are good")
end
Lua
函数
在Lua
中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。
Lua
提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。
Lua
函数主要有两种用途:
- 完成指定的任务,这种情况下函数作为调用语句使用;
- 计算并返回值,这种情况下函数作为赋值语句的表达式使用
函数定义
Lua
编程语言函数定义格式如下:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
解析:
- optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
- function_name: 指定函数名称。
argument1
,argument2
,argument3
…,argumentn
: 函数参数,多个参数以逗号隔开,函数也可以不带参数。- function_body: 函数体,函数中需要执行的代码语句块。
result_params_comma_separated
: 函数返回值,Lua
语言函数可以返回多个值,每个值以逗号隔开。
function max(num1,num2)
if num1>num2 then
return num1
else
return num2
end
end
print(max(1, 10))
print(max(100, 10))
匿名函数
max = function(num1, num2)
if num1 > num2 then
return num1
else
return num2
end
end
print(max(1, 10))
print(max(100, 10))
多返回值
Lua
函数可以返回多个结果值
fix = function(num1, num2)
return num1+num2, num1-num2
end
a, b = fix(10,5)
print(a.." and "..b)
可变参数
Lua
函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 … 表示函数有可变的参数
sum = function(...)
local s=0
tab = {...}
for k,v in ipairs(tab) do
s = s+v
end
print("总共传入 " .. select("#",...) .. " 个数")
return s
end
print(sum(1,2,3,4,5,6,8))
select("#",…)获取可变参数的数量
有时候可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前
sum = function(first,...)
local s=0
tab = {...}
for k,v in ipairs(tab) do
s = s+v
end
print("总共传入 " .. select("#",...) .. " 个数")
return s
end
print(sum(1,2,3,4,5,6,8))
-
通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select(’#’, …) 或者 select(n, …)
-
- select(’#’, …) 返回可变参数的长度
- select(n, …) 用于返回 n 到 select(’#’,…) 的参数
-
调用 select 时,必须传入一个固定实参 selector(选择开关) 和一系列变长参数。如果 selector 为数字 n,那么 select 返回 n 后所有的参数,否则只能为字符串 #,这样 select 返回变长参数的总数。
-
do function foo(...) for i = 1, select('#', ...) do -->获取参数总数 local arg = select(i, ...); -->读取参数 print("arg", arg); end end foo(2, 3, 1, 4); end
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 输出结果 -10 |
关系运算符
下表列出了Lua
语言中的常用关系运算符,设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
== | 等于,检测两个值是否相等,相等返回 true,否则返回 false | (A == B) 为 false。 |
~= | 不等于,检测两个值是否相等,不相等返回 true,否则返回 false | (A ~= B) 为 true。 |
> | 大于,如果左边的值大于右边的值,返回 true,否则返回 false | (A > B) 为 false。 |
< | 小于,如果左边的值大于右边的值,返回 false,否则返回 true | (A < B) 为 true。 |
>= | 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false | (A >= B) 返回 false。 |
<= | 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false | (A <= B) 返回 true。 |
逻辑运算符
下表列出了 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。 |
其他运算符
下表列出了Lua
语言中的连接运算符与计算表或字符串长度的运算符:
操作符 | 描述 | 实例 |
---|---|---|
… | 连接两个字符串 | a…b ,其中 a 为 "Hello " , b 为 “World”, 输出结果为 “Hello World”。 |
# | 一元运算符,返回字符串或表的长度。 | #“Hello” 返回 5 |
Lua
字符串
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表一个反斜线字符’’’ | 092 |
’ | 代表一个单引号(撇号)字符 | 039 |
" | 代表一个双引号字符 | 034 |
\0 | 空字符(NULL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
字符串操作
Lua
提供了很多的方法来支持字符串的操作:
序号 | 方法 & | 用途 |
---|---|---|
1 | string.upper(argument) | 字符串全部转为大写字母。 |
2 | string.lower(argument) | 字符串全部转为小写字母。 |
3 | string.gsub(mainString,findString,replaceString,num) | 在字符串中替换。mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换) |
4 | string.find (str, substr, [init, [end]]) | 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil(经过实际测试,end参数没啥用) |
5 | string.reverse(arg) | 字符串反转> string.reverse("Lua") auL |
6 | string.format(...) | 返回一个类似printf 的格式化字符串 |
7 | string.char(arg) 和string.byte(arg[,int]) | char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。 |
8 | string.len(arg) | 计算字符串长度 |
9 | string.rep(string, n) | 返回字符串string的n个拷贝 |
10 | … | 链接两个字符串 |
11 | string.gmatch(str, pattern) | 回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。 |
12 | string.match(str, pattern, init) | string.match() 只寻找源字串str 中的第一个配对. 参数init 可选, 指定搜寻过程的起点, 默认为1。 在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。 |
字符串截取
string.sub(mainstring,init[,end])
mainstring
截取目标字符串
init
截取开始位置
end
截取结束位置
source_string = "I am here waiting for you!"
print(string.sub(source_string,1,10))
-- 截取最后10个字符
print(string.sub(source_string,-10))
-- 引用越界 直接输出原始字符串
print(string.sub(source_string,-100))
字符串大小写转换
字符串转大写
print(string.upper("Lua"))
####字符串转小写
print(string.lower("Lua"))
字符串查找与反转
字符串查找
print(string.find("Lua World","or",5))
字符串反转
print(string.find("Lua World"))
字符串格式化
格式字符串可能包含以下的转义码:
- %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位.
字符与整数相互转换
字符转整数
print(string.byte("A"))
整数转字符
print(string.char(65))
其他常用函数
获取字符串长度
print(string.len("Lua World"))
字符串多次复制
repeatString = string.rep("Lua World",2)
print(repeatString)
匹配模式(正则表达)
单个字符(除 ^$()%.[]*±? 外): 与该字符自身配对
- .(点): 与任何字符配对
- %a: 与任何字母配对
- %c: 与任何控制符配对(例如\n)
- %d: 与任何数字配对
- %l: 与任何小写字母配对
- %p: 与任何标点(punctuation)配对
- %s: 与空白字符配对
- %u: 与任何大写字母配对
- %w: 与任何字母/数字配对
- %x: 与任何十六进制数配对
- %z: 与任何代表0的字符配对
- %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*±?)的配对问题, 例如%%与%配对
模式条目可以是:
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 ‘
*
’, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 ‘
+
’, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 ‘
-
’, 将匹配零或更多个该类的字符。 和 ‘*
’ 不同, 这个条目总是匹配尽可能短的串; - 单个字符类跟一个 ‘
?
’, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个; %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
’ 一样。
模式:
模式 指一个模式条目的序列。
在模式最前面加上符号 ‘^
’ 将锚定从字符串的开始处做匹配。
在模式最后面加上符号 ‘$
’ 将使匹配过程锚定到字符串的结尾。
如果 ‘^
’ 和 ‘$
’ 出现在其它位置,它们均没有特殊含义,只表示自身。
捕获:
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。
当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。
例如,对于模式 "(a(.)%w(%s))"
, 字符串中匹配到 "a(.)%w(%s)"
的部分保存在第一个捕获物中 (因此是编号 1 ); 由 “.
” 匹配到的字符是 2 号捕获物, 匹配到 “%s*
” 的那部分是 3 号。
作为一个特例,空的捕获 ()
将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 "()aa()"
作用到字符串 "flaaap"
上,将产生两个捕获物: 3 和 5 。
Lua
数组
####一维数组
一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用for循环出数组中的元素
array = { "Mike","John","Sarah","Hans"}
for i = 0,2 do
print(array[i])
end
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组。
以下是一个三行三列的阵列二维数组:
array = {}
for i = 1,9 do
array[i] = {}
for j = 1,i do
array[i][j] = i*j
end
end
for i = 1,9 do
for j = 1,i do
print(i.."*"..j.."="..array[i][j])
end
end
Lua
迭代器
泛型 for 迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in pairs(t) do
print(k, v)
end
上面代码中,k, v为变量列表;pairs(t)为表达式列表
className={stu1="Mike",stu2="Bob",stu3="Sarah"}
for key, value in pairs(className) do
print(key, value)
end
className={"Mike","Bob","Sarah"}
for key, value in pairs(className) do
print(key, value)
end
以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。
下面我们看看泛型 for 的执行过程:
- 首先,初始化,计算 in 后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是 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 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
array = {"Google", "Runoob"}
function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
map = function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
return map
end
for element in elementIterator(array)
do
print(element)
end
Lua table
表
Lua table
使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table
是不固定大小的
Lua
也是通过table来解决模块(module)、包(package)和对象(Object)的
-- 初始化表
mytable = {}
-- 指定表内数据
mytable[1] = "Mike"
mytable["key"] = "value"
for k,v in pairs(mytable) do
print(k.."-->"..v)
end
copytable = mytable
for k,v in pairs(copytable) do
print(k.."-->"..v)
end
-- 移除引用
mytable = nil
-- Lua 垃圾回收会释放内存
if (type(mytable)~="nil") then
for k,v in pairs(mytable) do
print(k.."-->"..v)
end
else
print("输入对象为空")
end
for k,v in pairs(copytable) do
print(k.."-->"..v)
end
当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua
的垃圾回收机制会清理相对应的内存
Table 操作
以下列出了 Table 操作常用的方法:
序号 | 方法 | 用途 |
---|---|---|
1 | table.concat (table [, sep [, start [, end]]]) | concat 是concatenate(连锁, 连接)的缩写. table.concat() 函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep )隔开。 |
2 | table.insert (table, [pos,] value): | 在table的数组部分指定位置(pos )插入值为value的一个元素. pos 参数可选, 默认为数组部分末尾. |
3 | table.maxn (table) | 指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2 之后该方法已经不存在了,本文使用了自定义函数实现) |
4 | table.remove (table [, pos]) | 返回table数组部分位于pos 位置的元素. 其后的元素会被前移.pos 参数可选, 默认为table长度, 即从最后一个元素删起。 |
5 | table.sort (table [, comp]) | 对给定的table进行升序排序。 |
Table 连接
我们可以使用 concat()
输出一个列表中元素连接成的字符串
data = {"apple","banana","orange","watermelon"}
print(table.concat(data,":",1,2))
print(table.concat(data,":",1,4))
Table插入和移除
table插入数据
data = {"apple","banana","orange","watermelon"}
table.insert(data, 2, "peanapple")
for k,v in ipairs(data) do
print(k.."-->"..v)
end
table删除数据
data = {"apple","banana","orange","watermelon"}
table.insert(data, 2, "peanapple")
for k,v in ipairs(data) do
print(k.."-->"..v)
end
table.remove(data,1)
for k,v in ipairs(data) do
print(k.."-->"..v)
end
Lua
模块与包
模块类似于一个封装库,从Lua 5.1
开始,Lua
加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API
接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua
的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua
,文件代码格式如下
module = {}
module.name = "Mike"
function module.getName()
print("我的名字是"+module.name)
end
function changeName()
print("这是私有函数")
end
function module.show()
changeName()
print("这是共有函数")
end
return module
require 函数
Lua
提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了
res = require("module")
res.show()
print(res.name)
-- res.changeName()
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 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
元表(Metatable
)
在 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
)。
以下实例演示了如何对指定的表设置元表:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
以上代码也可以直接写成一行:
mytable = setmetatable({},{})
以下为返回对象元表:
getmetatable(mytable) -- 这回返回mymetatable
可以发现getmetatable(mytable)
返回的是元表
mytable ={}
mymetatable = {}
setmetatable(mytable, mymetatable)
print(mymetatable)
print(getmetatable(mytable))
__index 元方法
这是 metatable
最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值(索引值不存在),那么Lua
就会寻找该table的metatable
(假定有metatable
)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用 lua
命令进入交互模式查看:
>$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
\> other = { foo = 3 }
\> t = setmetatable({}, { __index = other })
\> t.foo
3
\> t.bar
nil
mytable ={foo=3, age=15, height=158}
mymetatable = {weight=200}
setmetatable(mytable, {
__index = mymetatable
})
print(mytable.foo)
print(mytable.weight)
print(mytable.son)
如果__index包含一个函数的话,Lua
就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。
实例
mytable = setmetatable({key1="value1"},{
__index = function(mytable,key)
for k,v in pairs(mytable) do
print(k,v)
end
print('----------')
print(key)
print('----------')
if key=="key2" then
return "metatable"
elseif key=="key1" then
return mytable[key]
else
return mytable[key]
end
end
})
print(mytable.key1,mytable.key2)
实例输出结果为:
key1 value1
----------
key2
----------
value1 metatable
实例解析:
-
mytable 表赋值为 {key1 = "value1"}
-
mytable 设置了元表,元方法为 __index
-
在mytable表中查找 key1,如果找到,返回该元素,找不到则继续
-
在mytable表中查找 key2,如果找到,返回 metatablevalue,找不到则继续
-
判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数
-
元方法中查看是否传入 "key2" 键的参数(mytable.key2已设置),如果传入 "key2" 参数返回 "metatablevalue",否则返回 mytable 对应的键值
我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
总结
Lua
查找一个表元素时的规则,其实就是如下 3 个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
__newindex
元方法
__newindex
元方法用来对表更新,__index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex
元方法:如果存在则调用这个函数而不进行赋值操作。
以下实例演示了 __newindex
元方法的应用:
实例
mymetatable = {}
mytable = setmetatable({
key1="value1"
}, {
__newindex=mymetatable,
__index=function(mytable,key)
if key=="key2" then
print("key2 is searching")
end
end
})
print(mytable.key1)
mytable.newKey = "newValue"
print(mytable.newKey,mymetatable.newKey)
mytable.key1 = "value2"
print(mytable.key1,mymetatable.Key1)
mytable.key2 = "here"
print(mytable.key2,mymetatable.Key2)
以上实例执行输出结果为:
value1
nil newValue
value2 nil
key2 is searching
nil nil
以上实例中表设置了元方法 __newindex
,在对新索引键(newkey
)赋值时(mytable.newkey
= “新值2”),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1
),则会进行赋值,而不调用元方法 __newindex
。
以下实例使用了 rawset
函数来更新表:
rawset
实例
mytable = setmetatable({key1="value1"},{
__newindex = function(mytable,key,value)
rawset(mytable,key,value)
end
})
print(mytable.key1,mytable.key2)
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1,mytable.key2)
以上实例执行输出结果为:
value1 nil
new value 4
为表添加操作符
以下实例演示了两表相加操作:
实例
– 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
– 自定义计算表中最大键值函数 table_maxn
,即计算表的元素个数
function table_maxn(t)
local mn = 0
for k,v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
mytable = setmetatable({1, 2, 3, 4}, {
__add = function(mytable, newtable)
for i = 1,table_maxn(newtable) do
table.insert(mytable,table_maxn(mytable)+1,newtable[i])
end
return mytable
end
})
othertable = {7, 8, 9, 10}
mytable = mytable + othertable
for k,v in ipairs(mytable) do
print(k.."-->"..v)
end
以上实例执行输出结果为:
1-->1
2-->2
3-->3
4-->4
5-->7
6-->8
7-->9
8-->10
add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(**注意:******是两个下划线)
模式 | 描述 |
---|---|
__add | 对应的运算符 ‘+’. |
__sub | 对应的运算符 ‘-’. |
__mul | 对应的运算符 ‘*’. |
__div | 对应的运算符 ‘/’. |
__mod | 对应的运算符 ‘%’. |
__unm | 对应的运算符 ‘-’. |
__concat | 对应的运算符 ‘…’. |
__eq | 对应的运算符 ‘==’. |
__lt | 对应的运算符 ‘<’. |
__le | 对应的运算符 ‘<=’. |
__call 元方法
__call 元方法在Lua
调用一个值时调用。以下实例演示了计算表中元素的和:
实例
– 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
– 自定义计算表中最大键值函数 table_maxn
,即计算表的元素个数
function table_maxn(t)
local mn = 0
for k,v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
mytable = setmetatable({10, 5, 9}, {
__call = function(mytable,newtable)
local sum = 0
for i = 1,table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1,table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {1, 2, 3, 4}
print(mytable(newtable))
以上实例执行输出结果为:
34
__tostring
元方法
__tostring
元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容:
实例
mytable = setmetatable({10, 20, 30}, {
__tostring = function(mytable)
local sum = 0
for k,v in pairs(mytable) do
sum = sum + v
end
return string.format("the sum of value in table is %d",sum)
end
})
print(mytable)
以上实例执行输出结果为:
the sum of value in table is 60
从本文中我们可以知道元表可以很好的简化我们的代码功能,所以了解Lua
的元表,可以让我们写出更加简单优秀的 Lua
代码。
Lua
协同程序(coroutine)
什么是协同(coroutine)?
Lua
协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同是非常强大的功能,但是用起来也很复杂。
线程和协同程序区别(线程和协程的区别)
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
基本语法
方法 | 描述 |
---|---|
coroutine.create () | 创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用 |
coroutine.resume () | 重启 coroutine,和 create 配合使用 |
coroutine.yield () | 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果 |
coroutine.status () | 查看 coroutine 的状态 注:coroutine 的状态有三种:dead,suspended,running,具体什么时候有这样的状态请参考下面的程序 |
coroutine.wrap () | 创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复 |
coroutine.running () | 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号 |
以下实例演示了以上各个方法的用法:
coroutine_test.lua
文件
co = coroutine.create(
function(index)
print(index, coroutine.status(co))
end
)
print(coroutine.status(co))
coroutine.resume(co, 1)
print(coroutine.status(co))
print("------")
co = coroutine.wrap(
function(index)
print(index)
end
)
co(1)
print("------")
co1 = coroutine.create(
function()
for i = 1,10 do
print(string.format("current value of i is %d",i))
if i==3 then
print(coroutine.status(co1))
print(coroutine.running())
end
coroutine.yield()
end
end
)
coroutine.resume(co1)
coroutine.resume(co1)
coroutine.resume(co1)
print(coroutine.status(co1))
print(coroutine.running())
print("------")
coroutine.resume(co1)
以上实例执行输出结果为:
suspended
1 running
dead
------
1
------
current value of i is 1
current value of i is 2
current value of i is 3
running
thread: 00A11D90
suspended
nil
------
current value of i is 4
coroutine.running
就可以看出来,coroutine在底层实现就是一个线程。
当create一个coroutine的时候就是在新线程中注册了一个事件。
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
接下来我们分析一个更详细的实例:
实例
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2*a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
以上实例执行输出结果为:
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
以上实例接下如下:
- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
- 协同程序运行;
- 运行到yield语句;
- yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
- 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
- yield返回;
- 协同程序继续运行;
- 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine
co = coroutine.create(
function(index)
print(index)
return index
end
)
print(coroutine.resume(co,10))
resume返回值研究
根据上面的程序输出我们可以知道,resume至少有一个返回值(state,result1,result2,result3...resultn)
,其中第一个是该协同程序是否能运行,后面的参数时运行函数时的返回值
resume和yield的配合强大之处在于:
- resume处于主程中,它将外部状态(数据)传入到协同程序内部
- yield则将内部的状态(数据)返回到主程中
生产者-消费者问题
现在我就使用Lua
的协同程序来完成生产者-消费者这一经典问题。
local newProductor
function productor()
local i = 0
while true do
i = i + 1
send(i)
end
end
function send(number)
coroutine.yield(number)
end
function consumer()
while true do
local i = receive()
print(i)
end
end
function receive()
local status,value = coroutine.resume(newProductor)
return value
end
newProductor = coroutine.create(productor)
consumer()
以上实例执行输出结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
……
Lua
文件 I/O
Lua
I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
打开文件操作语句语法:
file = io.open (filename [, mode])
mode 的值有:
模式 | 描述 |
---|---|
r | 以只读方式打开文件,该文件必须存在。 |
w | 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。 |
a | 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) |
r+ | 以可读写方式打开文件,该文件必须存在。 |
w+ | 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 |
a+ | 与a类似,但此文件可读可写 |
b | 二进制模式,如果文件是二进制文件,可以加上b |
+ | 号表示对文件既可以读也可以写 |
简单模式
简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
以下为 file.lua
文件代码,操作的文件为module.lua
(如果没有你需要创建该文件),代码如下:
-- 以只读方式打开文件
file = io.open("module.lua", "r")
-- 设置默认输入文件为 test.lua
io.input(file)
-- 输出文件第一行
print(io.read())
-- 关闭打开的文件
io.close(file)
-- 以附加的方式打开只写文件
file = io.open("module.lua", "a")
-- 设置默认输出文件为 test.lua*
io.output(file)
-- 在文件最后一行添加 Lua 注释
io.write("-- test.lua 文件末尾注释")
-- 关闭打开的文件
io.close(file)
执行以上代码,你会发现,输出了 module.lua
文件的第一行信息,并在该文件最后一行添加了 lua
的注释。如我这边输出的是:
–
test.lua
文件
file = io.open("module.lua","r")
io.input(file)
line_message = io.read()
data = ""
while line_message do
data = string.format("%s\n%s",data,line_message)
line_message = io.read()
end
print(data)
io.close(file)
在以上实例中我们使用了 io."x"
方法,其中io.read()
中我们没有带参数,参数可以是下表中的一个:
模式 | 描述 |
---|---|
“*n” | 读取一个数字并返回它。例:file.read("*n") number |
“*a” | 从当前位置读取整个文件。例:file.read("*a") all |
“*l”(默认) | 读取下一行,在文件尾(EOF) 处返回 nil。例:file.read("*l") |
number | 返回一个指定字符个数的字符串,或在EOF 时返回 nil。例:file.read(5) |
其他的 io
方法有:
io.tmpfile()
返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除io.type(file)
检测obj是否一个可用的文件句柄io.flush()
向文件写入缓冲中的所有数据io.lines(optional file name)
返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件
完全模式
通常我们需要在同一时间处理多个文件。我们需要使用file:function_name
来代替io.function_name
方法。以下实例演示了如何同时处理同一个文件:
file = io.open("module.lua", "r")
print(file:read())
file:close()
file = io.open("module.lua", "a")
file:write("--testing")
file:close()
执行以上代码,你会发现,输出了module.lua
文件的第一行信息,并在该文件最后一行添加了 lua
的注释。如我这边输出的是:
-- testing
read 的参数与简单模式一致。
其他方法:
-
file:seek(optional whence, optional offset):
设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:- “set”: 从文件头开始
- “cur”: 从当前位置开始[默认]
- “end”: 从文件尾开始
- offset:默认为0
不带参数
file:seek("cur")
,则返回当前位置file:seek("set")
则定位到文件头file:seek("end")
则定位到文件尾并返回文件大小file = io.open("module.lua", "r") file:seek("set",2) print(file:read("*l")) file:seek("cur",2) print(file:read("*l")) file:close()
-
file:flush():
向文件写入缓冲中的所有数据 -
io.lines(optional file name):
打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。
若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如:for line in io.lines("module.lua") do print(line) end
以下实例使用了 seek 方法,定位到文件倒数第 25 个位置并使用 read 方法的 *a 参数,即从当期位置(倒数第 25 个位置)读取整个文件。
实例
file = io.open("module.lua")
file:seek("end",-25)
print(file:read("*a"))
file:close()
我这边输出的结果是:
lua 文件末尾注释--testing
Lua
错误处理
程序运行中错误处理是必要的,在我们进行文件操作,数据转移及web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。
任何程序语言中,都需要错误处理。错误类型有:
- 语法错误
- 运行错误
语法错误
语法错误通常是由于对程序的组件(如运算符、表达式)使用不当引起的。一个简单的实例如下:
-- test.lua 文件
a == 2
以上代码执行结果为:
lua: demo01.lua:735: '=' expected near '=='
正如你所看到的,以上出现了语法错误,一个 “=” 号跟两个 “=” 号是有区别的。一个 “=” 是赋值表达式两个 “=” 是比较运算。
另外一个实例:
实例一
for i = 1,10
print(i)
end
执行以上程序会出现如下错误:
lua: demo01.lua:738: 'do' expected near 'print'
语法错误比程序运行错误更简单,运行错误无法定位具体错误,而语法错误我们可以很快的解决,如以上实例我们只要在for语句下添加 do 即可:
实例一修正
for a= 1,10,1 do
print(a)
end
运行错误
运行错误是程序可以正常执行,但是会输出报错信息。如下实例由于参数输入错误,程序执行时报错:
function add(a,b)
return a+b
end
add(10)
当我们编译运行以下代码时,编译是可以成功的,但在运行的时候会产生如下错误:
lua: demo01.lua:744: attempt to perform arithmetic on local 'b' (a nil value)
stack traceback:
demo01.lua:744: in function 'add'
demo01.lua:747: in main chunk
[C]: ?
lua
里调用函数时,即使实参列表和形参列表不一致也能成功调用,多余的参数会被舍弃,缺少的参数会被补为 nil。
以上报错信息是由于参数 b 被补为 nil 后,nil 参与了 + 运算。
假如 add 函数内不是 “return a+b” 而是 “print(a,b)” 的话,结果会变成 “10 nil” 不会报错。
错误处理
我们可以使用两个函数:assert 和 error 来处理错误。实例如下:
assert(断言)实例
function add(a,b)
assert(type(a)=="number","a is not a number")
assert(type(b)=="number","b is not a number")
return a+b
end
add(10)
执行以上程序会出现如下错误:
lua: demo01.lua:745: b is not a number
stack traceback:
[C]: in function 'assert'
demo01.lua:745: in function 'add'
demo01.lua:749: in main chunk
[C]: ?
实例中assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。
error函数
语法格式:
error (message [, level])
功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)
通常情况下,error会附加一些错误位置的信息到message头部。
Level参数指示获得错误的位置:
- Level=1[默认]:为调用error位置(文件+行号)
- Level=2:指出哪个调用error的函数的函数
- Level=0:不添加错误位置信息
function add(a,b)
if (type(a)~="number") then
error("a is not a number",1)
end
if (type(b)~="number") then
error("b is not a number",1)
end
return a+b
end
add(10)
pcall
和 xpcall
、debug
pcall
Lua
中处理错误,可以使用函数pcall
(protected call)来包装需要执行的代码。
pcall
接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo
。
语法格式如下
if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end
简单实例:
pcall
实例
> =
pcall
(function(index) print(index) end,33)
33
true
> =pcall
(function(index) print(index) error(‘error…’) end,33)
33
falsestdin:1
: error…
pcall
以一种"保护模式"来调用第一个参数,因此pcall
可以捕获函数执行中的任何错误。
pcall
调用的不足
通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall
返回时,它已经销毁了调用桟的部分内容。
####xpcall
Lua
提供了xpcall
函数
xpcall
接收第二个参数——一个错误处理函数,当错误发生时,Lua
会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
debug.debug
:提供一个Lua
提示符,让用户来检查错误的原因debug.traceback
:根据调用桟来构建一个扩展的错误消息
print(xpcall(function(index)
print(index)
error("error...",1)
end, function()
print(debug.traceback())
end, 33))
nil
stack traceback:
demo01.lua:771: in function <demo01.lua:770>
[C]: in function 'error'
demo01.lua:769: in function <demo01.lua:767>
[C]: in function 'xpcall'
demo01.lua:767: in main chunk
[C]: ?
false nil
xpcall
使用实例 2:
function myFunction()
n = n/nil
end
function myErrorHanlder(err)
print("Error",err)
end
status = xpcall(myFunction,myErrorHanlder)
print(status)
执行以上程序会出现如下错误:
Error demo01.lua:777: attempt to perform arithmetic on global 'n' (a nil value)
false
Lua
调试(Debug)
Lua
提供了 debug 库用于提供创建我们自定义调试器的功能。Lua
本身并未有内置的调试器,但很多开发者共享了他们的 Lua
调试器代码。
Lua
中 debug 库包含以下函数:
序号 | 方法 | 用途 |
---|---|---|
1. | debug() | 进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。 输入一行仅包含 cont 的字符串将结束这个函数, 这样调用者就可以继续向下运行。 |
2. | getfenv(object) | 返回对象的环境变量。 |
3. | gethook(optional thread) | 返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数 |
4. | getinfo ([thread,] f [, what]) | 返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字 f 表示该函数。 数字 f 表示运行在指定线程的调用栈对应层次上的函数: 0 层表示当前函数(getinfo 自身); 1 层表示调用 getinfo 的函数(除非是尾调用,这种情况不计入栈);等等。 如果 f 是一个比活动函数数量还大的数字, getinfo 返回 nil。 |
5. | debug.getlocal ([thread,] f, local) | 此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。 |
6. | getmetatable(value) | 把给定索引指向的值的元表压入堆栈。如果索引无效,或是这个值没有元表,函数将返回 0 并且不会向栈上压任何东西。 |
7. | getregistry() | 返回注册表表,这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。 |
8. | getupvalue (f, up) | 此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。 以 ‘(’ (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。 |
10. | sethook ([thread,] hook, mask [, count]) | 将一个函数作为钩子函数设入。 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义:’c ’: 每当 Lua 调用一个函数时,调用钩子;’r ’: 每当 Lua 从一个函数内返回时,调用钩子;’l ’: 每当 Lua 进入新的一行时,调用钩子。 |
11. | setlocal ([thread,] level, local, value) | 这个函数将 value 赋给 栈上第 level 层函数的第 local 个局部变量。 如果没有那个变量,函数返回 nil 。 如果 level 越界,抛出一个错误。 |
12. | setmetatable (value, table) | 将 value 的元表设为 table (可以是 nil)。 返回 value。 |
13. | setupvalue (f, up, value) | 这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字。 |
14. | traceback ([thread,] [message [, level]]) | 如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。 |
上表列出了我们常用的调试函数,接下来我们可以看些简单的例子:
function myfunction()
-- 信息在堆栈中的回溯
print(debug.traceback("Stack trace", "here"))
-- 0层表示当前函数(getinfo自身)
print("--getinfo(0)--")
print(debug.getinfo(0))
-- 1层表示调用getinfo的函数(除非是尾调用,这种情况不计入栈)
print(debug.getinfo(1))
-- f是一个比活动函数数量还大的数字,getinfo但会nil
print("Stack trace end")
return 10
end
myfunction()
print(myfunction)
print(debug.getinfo(1))
执行以上代码输出结果为:
Stack trace
stack traceback:
demo01.lua:790: in function 'myfunction'
demo01.lua:796: in main chunk
[C]: ?
table: 00AE9A78
Stack trace end
table: 00AE9B18
在以实例中,我们使用到了 debug 库的 traceback 和 getinfo
函数, getinfo
函数用于返回函数信息的表。
另一个实例
我们经常需要调试函数的内的局部变量。我们可以使用 getupvalue
函数来设置这些局部变量。实例如下:
function newCounter()
local n = 0
local k = 0
return function()
k = n
n = n + 1
return n
end
end
counter = newCounter()
print(counter())
print(counter())
local i = 1
repeat
name, val = debug.getupvalue(counter, i)
if name then
print("index",i,name,"=",val)
if (name=="n") then
debug.setupvalue(counter,2,10)
end
i = i + 1
end
until not name
print(counter)
name, val = debug.getupvalue(counter,2)
print("index",i-1,name,"=",val)
执行以上代码输出结果为:
1
2
index 1 k = 1
index 2 n = 2
function: 00709B18
index 3 n = 10
在以上实例中,计数器在每次调用时都会自增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
设为收集器的 间歇率。 返回 间歇率 的前一个值。collectgarbage("setstepmul"):
返回 步进倍率 的前一个值。collectgarbage("step"):
单步运行垃圾收集器。 步长"大小"由arg
控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于Lua
分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。collectgarbage("stop"):
停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
以下演示了一个简单的垃圾回收实例:
mytable = {"apple","banana","orange"}
print(collectgarbage("count"))
mytable = nil
print(collectgarbage("count"))
print(collectgarbage("collect"))
print(collectgarbage("count"))
执行以上程序,输出结果如下(注意内存使用的变化):
20.9716796875
21.0009765625
0
19.4150390625
Lua
面向对象
面向对象编程(Object Oriented Programming,OOP
)是一种非常流行的计算机编程架构。
以下几种编程语言都支持面向对象编程:
- C++
- Java
- Objective-C
- Smalltalk
- C#
- Ruby
面向对象特征
- 1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 4)抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
Lua
中面向对象
我们知道,对象由属性和方法组成。LUA
中最基本的结构是table,所以需要用table来描述对象的属性。
Lua
中的 function 可以用来表示方法。那么LUA
中的类可以通过 table + function 模拟出来。
至于继承,可以通过 metetable
模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。
Lua
中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
account = {balance = 0}
function account.withdraw(content)
account.balance = account.balance - content
end
-- 这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,下面我们可以这样调用:
account.withdraw(10)
print(account.balance)
执行以上程序,输出结果如下(注意内存使用的变化):
-10
一个简单实例
以下简单的类包含了三个属性: area
, length
和breadth
,printArea
方法用于打印计算结果:
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
print(Rectangle)
-- 派生类的方法 new
function Rectangle:new(o,length,breadth)
o = o or {}
print(o)
for k,v in pairs(o) do
print(k.."-->"..v)
end
setmetatable(o, self)
print(self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
r = Rectangle:new(nil,10,20)
访问属性
我们可以使用点号(.)来访问类的属性:
print(r.length)
访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
内存在对象初始化时分配。
完整实例
以下我们演示了 Lua
面向对象的完整实例:
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
print(Rectangle)
-- 派生类的方法 new
function Rectangle:new(o,length,breadth)
o = o or {}
print(o)
for k,v in pairs(o) do
print(k.."-->"..v)
end
setmetatable(o, self)
print(self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
r = Rectangle:new(nil,10,20)
print(r.length)
r:printArea()
执行以上程序,输出结果为:
矩形面积为 200
Lua
继承
继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。
以下演示了一个简单的继承实例:
-- Meta class
Shape = { area-0 }
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
function Shape:printArea()
print("面积为", self.area)
end
接下来的实例,Square 对象继承了 Shape 类:
Square = Shape:new()
function Square:new(o, seide)
o = o or Shape:new(o, side)
setmetatable(o, self)
self.__index=self
return o
end
完整实例
以下实例我们继承了一个简单的类,来扩展派生类的方法,派生类中保留了继承类的成员变量和方法:
-- Meta class
Shape = { area=0 }
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
function Shape:printArea()
print("面积为"..self.area)
end
myshape = Shape:new(nil, 10)
-- print(myshape.area)
myshape:printArea()
Square = Shape:new()
function Square:new(o, side)
o = o or Shape:new(o, side)
setmetatable(o, self)
self.__index=self
return o
end
function Square:printArea()
print("正方形面积为",self.area)
end
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape: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
function Rectangle:printArea()
print("矩形面积为 ",self.area)
end
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
执行以上代码,输出结果为:
面积为 100
正方形面积为 100
矩形面积为 200
函数重写
Lua
中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
– 派生类方法 printArea
function Square:printArea ()
print("正方形面积 ",self.area)
end
Lua
数据库访问
本文主要为大家介绍 Lua
数据库的操作库:LuaSQL。他是开源的,支持的数据库有:ODBC
, ADO, Oracle, MySQL
, SQLite
和 PostgreSQL
。
本文为大家介绍MySQL
的数据库连接。
LuaSQL
可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。
LuaRocks
安装方法:
$ wget http:**//luarocks.org/releases/luarocks-2.2.1.tar.gz
$ tar zxpf luarocks-2.2.1.tar.gz
$ cd luarocks-2.2.1
$ ./**configure; sudo make bootstrap
$ sudo luarocks install luasocket
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> require “socket”
Window 下安装 LuaRocks:https://github.com/keplerproject/luarocks/wiki/Installation-instructions-for-Windows
安装不同数据库驱动:
luarocks install luasql-sqlite3
luarocks install luasql-postgres
luarocks install luasql-mysql
luarocks install luasql-sqlite
luarocks install luasql-odbc
你也可以使用源码安装方式,Lua Github
源码地址:https://github.com/keplerproject/luasql
Lua
连接MySql
数据库:
实例
require "luasql.mysql"
--创建环境对象
env = luasql.mysql()
--连接数据库
conn = env:connect("数据库名","用户名","密码","IP地址",端口)
--设置数据库的编码格式
conn:execute"SET NAMES UTF8"
--执行数据库操作
cur = conn:execute("select * from role")
row = cur:fetch({},"a")
--文件对象的创建
file = io.open("role.txt","w+");
while row do
var = string.format("%d %s\n", row.id, row.name)
print(var)
file:write(var)
row = cur:fetch(row,"a")
end
file:close() *--关闭文件对象*
conn:close() *--关闭数据库连接*
env:close() *--关闭数据库环境*
o, side)
setmetatable(o, self)
self.__index=self
return o
end
### 完整实例
以下实例我们继承了一个简单的类,来扩展派生类的方法,派生类中保留了继承类的成员变量和方法:
```lua
-- Meta class
Shape = { area=0 }
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
function Shape:printArea()
print("面积为"..self.area)
end
myshape = Shape:new(nil, 10)
-- print(myshape.area)
myshape:printArea()
Square = Shape:new()
function Square:new(o, side)
o = o or Shape:new(o, side)
setmetatable(o, self)
self.__index=self
return o
end
function Square:printArea()
print("正方形面积为",self.area)
end
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape: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
function Rectangle:printArea()
print("矩形面积为 ",self.area)
end
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
执行以上代码,输出结果为:
面积为 100
正方形面积为 100
矩形面积为 200
函数重写
Lua
中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
– 派生类方法 printArea
function Square:printArea ()
print("正方形面积 ",self.area)
end
Lua
数据库访问
本文主要为大家介绍 Lua
数据库的操作库:LuaSQL。他是开源的,支持的数据库有:ODBC
, ADO, Oracle, MySQL
, SQLite
和 PostgreSQL
。
本文为大家介绍MySQL
的数据库连接。
LuaSQL
可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。
LuaRocks
安装方法:
$ wget http:**//luarocks.org/releases/luarocks-2.2.1.tar.gz
$ tar zxpf luarocks-2.2.1.tar.gz
$ cd luarocks-2.2.1
$ ./**configure; sudo make bootstrap
$ sudo luarocks install luasocket
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> require “socket”
Window 下安装 LuaRocks:https://github.com/keplerproject/luarocks/wiki/Installation-instructions-for-Windows
安装不同数据库驱动:
luarocks install luasql-sqlite3
luarocks install luasql-postgres
luarocks install luasql-mysql
luarocks install luasql-sqlite
luarocks install luasql-odbc
你也可以使用源码安装方式,Lua Github
源码地址:https://github.com/keplerproject/luasql
Lua
连接MySql
数据库:
实例
require "luasql.mysql"
--创建环境对象
env = luasql.mysql()
--连接数据库
conn = env:connect("数据库名","用户名","密码","IP地址",端口)
--设置数据库的编码格式
conn:execute"SET NAMES UTF8"
--执行数据库操作
cur = conn:execute("select * from role")
row = cur:fetch({},"a")
--文件对象的创建
file = io.open("role.txt","w+");
while row do
var = string.format("%d %s\n", row.id, row.name)
print(var)
file:write(var)
row = cur:fetch(row,"a")
end
file:close() *--关闭文件对象*
conn:close() *--关闭数据库连接*
env:close() *--关闭数据库环境*