前言
出于热更新的需要,越来越多的Unity游戏项目都采用了Lua做为热更脚本,先不说众多的lua热更解决方案,也不说lua与unity如何结合及最佳实践,本文先主要介绍一下Lua的语法及自身特点:
什么是Lua
借用Lua官网介绍的一句:Lua是一个强大的,高效的,轻量的嵌入式脚本语言。支持过程编程,面向对象编程,函数式编程,数据驱动编程和数据描述。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua的特点
- 轻量级:它用标准C语言编写并以源代码的形式开放,编译后仅仅100多k,可以很方便的嵌入别的程序里。
- 可扩展:Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
- 其它特性:支持面向过程编程和函数式编程,自动内存管理,内置模式匹配,闭包,协同机制的多线程是,通过闭包和table可以很方便的支持面向对象编程所需要的一些关键机制。如数据抽象,虚函数,继承和重载等。
基本语法
注释
- 单行注释:
--
- 多行注释:
--[[xxxxx--]]
标识符
和大多数语言一样,区分大小写,字母,数字,下划线,不能有特殊字符
数据类型
- nil:就是null值,在条件语句中相当于false
- boolean:false和true
- number:数字类型
- string:字符串,用”“或”表示
- function:用C或Lua编写的函数
- userdata:任意存储在变量中的C数据结构
- thread:线程,用于执行协同程序
- table:本质上是一个关联数组,数组索引可以是数字或者是字符串。table的创建是通过“构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。
可以使用type函数测试值的类型
print(type(nil)) --nil
print(type(false)) --boolean
print(type(10)) --number
print(type('')) --string
print(type(function() end)) -- function
print(type({}))--table
print(type(coroutine.create(function() end)))--thread
注意的地方:nil作比较时应该加上双引号”:
type(X) == nil --false
type(X) == "nil" --true
string字符串:由一对双引号或单引用来表示,也可以用2个方括号[[]]表示一块字符串,对一个数字字串进行算数操作时,会尝试转成数字,字符串连接使用..,#计算字符串长度
s = 'hello'
html=[[
<html><header></header><body></body></html>
]]
print('2'+6) -- 8
print('a'..'b') -- ab
print(#'hello') -- 5
table:默认初始索引从1开始
**thread:主要的线程是协同程序(coroutine).它跟线程(thread)差不多,拥有独立的栈,局部变量和指令指针,和其他协同程序共享全局变量和其他大部分东西。
线程和协程的区别:线程可以同时运行多个,而协程做任意时刻只能运行一个,并且处于运行状态的协程只有被挂起时才会暂停。
userdata可以将任意C/C++的任意数据类型的数据(通常是struct和指针)存储到Lua变量中调用
变量
Lua变量有三种类型:分别是全局变量,局部变量和表中的域
默认是全局变量,除非用local显示声明为局部变量
赋值
可以对多个变量同时赋值,会先计算右边所有的值再执行赋值操作
a='hello'
a,b=10,2*x
x,y=y,x
索引
对table的索引使用方括号[].Lua也提供了.操作
Lua循环
- while:while(exp) do … end
- for:for var=exp1,exp2,exp3 do … end for … in do end
- repeat…until:repeat … until(exp)
分支控制
if(exp) then … elseif …else … end
Lua函数
[scope] function name(arg1,arg2,arg3…,argn)
body
return r1,r2
end
- 可以将函数作为参数传递给函数
- 可以将函数赋值给变量
- 可以返回多个值
- 可传递可变参数,使用三个点 … 表示
- 可将可变参数赋值给一个变量
- 可通过select(‘#’,…)获取可变参数的数量
- 可通过select(n,…)读取参数
Lua运算符
算术运算符
+-,*,/,%,^,-
关系运算符
==,~=(不等于),>,<,>=,<=
逻辑运算符
and,or,not
其它运算符
..(连接字符串),#(返回字符串或表的长度)
匹配模式
直接用常规的字符串描述
s = "Deadline is 12/12/2020,firm"
date="%d%d/%d%d/%d%d%d%d"
print(string.sub(s,string.find(s,date)))--12/12/2020
数组
全是用table来定义,下标从1开始,而不是通常的0.
- 一维数组:array={'lua','hello'}
- 多维数组:
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j]=i*j
end
end
其实本质就是用的table中的key一种形式
Lua迭代器
迭代器是一种对象,它能够遍历标准模板库容器中的部分或全部元素。迭代器是一种支持指针类型的结构。
- 泛型for迭代器:
for k,v in pairs(t) do
print(k,v)
end
for k,v in ipairs(array) do
print(k,v)
end
泛型for的执行过程:
1. 初始化,计算 in 后面表达式的值,表达式返回:迭代函数,状态常量,控制变量
2. 将状态常量和控制变量作为参数调用迭代函数
3. 将迭代函数返回的值赋给变量列表
4. 如果返回的第一个值为nil循环结束,否则执行循环体
5. 回到第2步
- 无状态的迭代器
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
iparis可以这样实现:
function iter(a,i)
i = i + 1
local v = a[i]
if v then
return i,v
end
end
function iparis(a)
return iter,a,0
end
- 多状态的迭代器:
迭代器保存多个状态而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法是将状态信息封装到table内。
array = {"lua","hello"}
function elementIterator(collection)
local index = 0
local count = #collection
return function()
index = index + 1
if index <= count
then
return collection[index]
end
end
end
for element in elementIterator(array)
do
print(element)
end
pairs和iparis的区别:
pairs迭代table,可以返回nil
ipairs迭代数组,不能返回nil,遇到nil则退出
Lua table
- table的构造:
t={}
t[1]='lua'
t=nil
t={[1]='lua'}
t={key='lua',fun=function() end}
- table的操作
table.concat,table.insert,table.remove,table.sort
* 注意 *当用#或table.getn获取长度时,都会在索引中断的地方停止计数,而获取到错误长度。
Lua 模块与包
模块类似封装库,模块是由变量函数等已知元素组成的table,创建一个模块就是创建一个table
module={}
module.constant = "常量"
function module.func1()
io.write('公有函数')
end
local function func2()
print('私有函数')
end
function module.func3()
func2()
end
return module
require函数用于加载模块。
require("<模块名>")
require "<模块名>"
local m = require("module")
print(m.constant)
C包
C包在使用以前必须首先加载并连接,最容易的实现方式是通过动态连接库机制。
local path = "/usr/local/lua/lib/libluasocket.so"
local f = assert(loadlib(path,"luaopen_socket"))
f() -- 真正打开库
Lua 元表(Metatable)
元表允许改变table的行为,每个行为关联了对应的元方法
如:定义两个table的相加操作a+b,先检查两者之一是否有元表,之后检查是否有一个叫”__add”的字段,找到则调用对应的值。
- setmetatable(table,metatable):设置元表
- getmetatable(table):返回对象的元表
mytable = {}
mymetatable={}
setmetatable(mytable,mymetatable)
- __index元方法:当通过键访问table时,如果键没有值,则会找table的metatable中的__index,如果__index包含一个表格,则在表格中找相应的键
other = {foo=3}
t=setmetatable({},{__index=other})
t.foo --3
t.bar --nil
- 如果__index包含一个函数,Lua就会调用那个函数,table和键会作为参数传递给函数
mytable=setmetatable({key1="value1"},{
__index = function(mytable,key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
总结:
lua查找一个表元素时的规则,为3个步骤:
1. 在表中查找,如果找到,返回该元素,找不到继续
2. 判断表中是否有元表,没有,则返回nil,有则继续
3. 判断有没有__index方法,如果__index方法为nil,则返回nil,如果__index方法是一个表,则重复1,2,3,如果__index是一个函数,则返回该函数的值
- __newindex元方法用于对表更新:当给表的一个缺少的索引赋值,解释器就会查找__newindex元方法,如存在,则不进行赋值
__newindex = function(mytable,key,value) rawset(mytable,key,"\""..value.."\"") end
- __add:两表相加 +
- __sub:两表相减 -
- __mul:两表相乘 *
- __div:两表相除 /
- __mod:两表取模 %
- __unm:取反 -
- __concat:连接 ..
- __eq:相等 =
- __lt: <
- __le: <=
- __call:在lua调用一个值时调用
__call=function(table,value) end
table(value)
- __tostring:修改表的输出行为
coroutine(协同程序)
- 协同程序与线程类似:拥有独立堆栈,独立局部变量,独立指令指针,与其它协同程序共享全局变量
- 与线程主要的区别是,线程可以同时运行多个,协同则需要协作运行,一个是系统态,一个是用户态。在等待同一个线程锁的几个线程有点类似协同
基本语法
- coroutine.create():创建coroutine,返回coroutine
- coroutine.resume():重启coroutine
- coroutine.yield():挂起coroutine
- coroutine.status():查看coroutine的状态
- coroutine.wrap():创建coroutine,返回一个函数
- corouting.running():返回正在运行的coroutine,即线程号
resume和yield配合强大之处在于,resume处于主线程中,将外部状态传入到协同程序内部,而yield则将内部的状态返回到主线程中
生产者-消费者问题
local newProductor
function productor()
local i = 0
while true do
i = i+1
send(i)
end
end
function consumer()
while true do
local i = receive()
print(i)
end
end
function send(x)
coroutine.yield(x)
end
function receive()
local status,value = coroutine.resume(newProductor)
return value
end
newProductor = coroutine.create(productor)
consumer()
Lua 文件 I/O
Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适
Lua 错误处理
使用两个函数:assert 和 error 来处理错误
local function add(a,b)
assert(type(a) == "number", "a 不是一个数字")
assert(type(b) == "number", "b 不是一个数字")
return a+b
end
add(10)
error (message [, level])
pcall 和 xpcall、debug
- pcall接收一个函数和要传递个后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo
- xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展看(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了
- debug库提供了两个通用的错误处理函数:
debug.debug:提供一个Lua提示符,让用户来检查错误的原因
debug.traceback:根据调用桟来构建一个扩展的错误消息
Lua 调试(Debug)
Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码
- 命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。
- 图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit
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”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
Lua 面向对象
- LUA中的类可以通过table + function模拟出来
- 继承,可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)
表的创建:
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance-v
end
Account.withdraw(100.00)
以下是一个简单的类:
-- Meta class
Rectangle={area=0,length=0,breadth=0}
-- 派生类的方法
function Rectangle:new(o,length,breadth)
o = o or {}
setmetatable(o,self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth
return o
end
function Rectangle:printArea()
print("the area is ",self.area)
end
创建对象
r = Rectangle:new(nil,10,20)
print(r.length)
r:printArea() -- 用冒号
完整实例
-- 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
function Shape:printArea()
print("the area is",self.area)
end
myshape = Shape:new(nil,10)
myshape:printArea()
继承
Square = Shape:new()
function Square:new(o,side)
o = o or Shape:new(o,side)
setmettable(o,self)
self.__index = self
return o
end
function Square:printArea() //函数重写
print('the area is ',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("the area is ",self.area)
end
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
Lua 数据库访问
Lua 数据库的操作库:LuaSQL。他是开源的,支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL
LuaSQL 可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动
#### 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() –关闭数据库环境
“`