Lua实验室之Lua语法

作者: koo叔 分类: Lua 发布时间: 2018-04-27 23:30 编辑

前言

出于热更新的需要,越来越多的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开始<br/>
**thread:主要的线程是协同程序(coroutine).它跟线程(thread)差不多,拥有独立的栈,局部变量和指令指针,和其他协同程序共享全局变量和其他大部分东西。<br/><br/>
线程和协程的区别:线程可以同时运行多个,而协程做任意时刻只能运行一个,并且处于运行状态的协程只有被挂起时才会暂停。<br/><br/>
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'}
  • 多维数组:<br/>
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的区别:<br/>
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<br/>

注意 当用#或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,即线程号<br/>
    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 采用了自动内存管理,实现了一个增量标记-扫描收集器<br/>
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模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)<br/>
    表的创建:
Account = {balance = 0}
function Account.withdraw(v)
    Account.balance = Account.balance-v
end
Account.withdraw(100.00)

以下是一个简单的类:<br/>

-- 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()   --关闭数据库环境

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

你的email不会被公开。必填项已用*标注

更多阅读
标签云