worldsite / blog.sc

Blogging soul chat, stay cool. via: https://blog.sc
3 stars 0 forks source link

Lua菜鸟教程 #21

Open suhao opened 4 years ago

suhao commented 4 years ago

Lua教程

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。

其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。其特性如下:

Lua基本语法

lua标识符与其他C++类似,区分大小写,保留字及lua内部全局变量多以下划线+大写字母组成(如_VERSION)。

1 true false nil
2 while for repeat
3 do end util return
4 if else elseif then
5 break goto
6 and not or
7 function in local

全局变量

默认情况下,变量总是全局的。不需要声明,赋值即创建;访问未初始化全局变量也不会出错,只是结果为 nil ;删除全局变量只需要将其赋值为nil即可。

数据类型

Lua是动态类型,变量不需要类型定义,只需要变量赋值。值可以存储在变量中,作为参数传递或者结果返回。

lua中有8种基本类型:nil、boolean、number、string、userdata、function、thread、table。 数据类型 描述
nil 一个无效值,如一个没有赋值的变量
对于全局变量和table,执行删除作用
做比较时需要加上双引号
boolean true或者false
nil看作是false,数字0为true
number 双精度实浮点数,double
默认类型可以修改luaconf.h里的定义
string 字符串,单双引号标识
可以使用[[]]表示一块多行字符串,如赋值为xml等时很有用
字符串进行算术操作,会尝试转为一个数字
字符串连接使用两个点".."
#用来计算字符串长度,放在字符串前
function 由c或者lua编写的函数
函数被看作第一类值(First-Class value)
可以以匿名函数的方式通过参数传递
userdata 用户自定义数据,存储在变量种,代表任意应用程序或者C/C++语言库所创建的数据结构及类型,可以将任意C/C++的任意数据类型的数据(通常是struct或者指针)存储在lua变量种
thread 执行的独立线路,执行协同程序
在lua中最主要的线程是协同程序(coroutine),和线程差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
table 表,关联数组,索引可以是数字、字符串或者表类型,通过构造表达式创建,如{}创建一个空表
lua的表默认初始化索引从1开始,不同于C++从0开始
不固定长度大小,新增数据自动增加长度,未初始化的table都是nil
支持[]和.点号两种索引方式

可以使用type函数来测试变量的类型和值的类型:

print(type(10.1*5))
print(type(print))

线程和协程的区别:线程可以同时多个运行,协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起时才能暂停。

变量

使用前必须代码中声明,创建变量。编译程序执行代码前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。

lua有三种变量类型:全局、局部、表中的域。语句块或者函数中的均为全局变量,local显式声明方为局部变量,局部作用域从声明位置开始到所在语句块结束。应尽可能使用局部变量,以避免命名冲突,且访问局部变量速度更快。

赋值时改变一个变量的值和改变表域的最基本方法,可以多变量同时赋值,用逗号隔开依次给左侧变量赋值。先计算右侧的值然后赋给左侧变量。

循环

循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。能否继续重复,决定循环的终止条件。循环语句是由循环体及循环的终止条件两部分组成的。

image

循环类型 描述
while循环 条件为true时重复执行,先检查后执行
for循环 重复执行次数在for中控制
repeat-until 重复执行,直到指定条件为真
循环嵌套 while do end; for do end; repeat until

流程控制

流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。

image

语句 描述
if语句 一个布尔表达式作为条件判断,后紧跟其他语句
if-else语句 if假执行else
if嵌套 在if-else-if中使用if-else-if

函数

函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。

Lua 编程语言函数定义格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

lua支持多返回值,用逗号隔开;可变参数类似C语言三个点号,{...}表示可变长参数组成的数组表,也可以用select("#", ...)来获取可变参个数;select(n, ...) 返回n到select(“#”, ...)的参数。

运算符

序号 类型 操作符 描述
1 算术运算符 +、-、*、/、%、-(负号)、^(乘幂, A^2=100)
2 关系运算符 ==、~=、>、<、>=、<=
3 逻辑运算符 and、or、not
4 其他运算符 两个点号(..连接字符串),#一元操作符返回字符串长度

字符串

字符串由数字、字母、下划线组成。可以使用单引号、双引号、[[]]三种形式定义。不能直接显示的字符,可以使用\转义。

格式字符串可能包含以下的转义码:

为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:

数组

数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。

迭代器

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。

表table

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。Lua table 是不固定大小的,你可以根据自己需要进行扩容。Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}

-- 定义一个常量
module.constant = "这是一个常量"

-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end

local function func2()
    print("这是一个私有函数!")
end

function module.func3()
    func2()
end

return module

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")

print(module.constant)

module.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;;"
#更新环境变量参数,使之立即生效
source ~/.profile

如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

Lua和C是很容易结合的,使用 C 为 Lua 写包。与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

元表(Metatable)

在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。

有两个很重要的函数来处理元表:

index 元方法是 metatable 最常用的键。当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的index 键。如果index包含一个表格,Lua会在表格中查找相应的键。index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。

Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

newindex 元方法用来对表更新,index则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。

协同程序

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("----------")

coroutine在底层实现就是一个线程;当create一个coroutine的时候就是在新线程中注册了一个事件; 当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件;coroutine.running() 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号。

文件IO

Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式:

错误处理

程序运行中错误处理是必要的,在我们进行文件操作,数据转移及web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。

我们可以使用两个函数:assert 和 error 来处理错误。assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出;error函数终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回),error会附加一些错误位置的信息到message头部。

Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。

pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。

if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end

pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。

调试

Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码。

垃圾回收

Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。

垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。

如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。

面向对象

LUA中最基本的结构是table,所以需要用table来描述对象的属性。lua 中的 function 可以用来表示方法。那么LUA中的类可以通过 table + function 模拟出来。至于继承,可以通过 metetable 模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。Lua 中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。

-- 元类
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()
-- 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()

数据库访问

Lua 数据库的操作库有一个开源的LuaSQL,支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL。LuaSQL 可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。

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

5.2 版本之后,require 不再定义全局变量,需要保存其返回值。

Lua参考手册