Lua初探

官方网站:http://www.lua.org/

三方模块:https://luarocks.org/

介绍
安装
luarocks
语法
注释
标示符
关键字
全局变量
数据类型
变量
赋值
索引
循环
流程控制
函数
可变参数
运算符
一元运算符,返回字符串或表的长度。 #”Hello” 返回 5
字符串
迭代器
泛型 for 迭代器
无状态的迭代器
多状态的迭代器
table
模块与包
require 函数
加载机制
C 包
元表(Metatable)
元方法
协同程序(coroutine)
线程和协同程序区别
基本语法
生产者-消费者问题
文件I/O
简单模式
完全模式
错误处理
assert
error
pcall 和 xpcall、debug
调试类型
垃圾回收
面向对象
数据库访问
参考

介绍

Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,ini等文件格式,并且更容易理解和维护。Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。 一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。

安装

curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz

tar zxf lua-5.3.0.tar.gz z代表gzip的压缩包;x代表解压;v代表显示过程信息;f代表后面接的是文件

cd lua-5.3.0

make linux test

make install

注意

报 lua.c:67:31: fatal error: readline/readline.h: No such file or directory,说明缺少libreadline-dev依赖包

centos: yum install readline-devel

debian: apt-get install libreadline-dev.

安装完成之后,Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。

$ lua -i

$ Lua 5.3.2 Copyright (C) 1994-2015 Lua.org, PUC-Rio

luarocks

luarocks可以安装和更新lua的第三方模块。

可以在 http://luarocks.org/releases/ 页面选择需要的软件包。

安装教程:https://github.com/keplerproject/luarocks/wiki/Installation-instructions-for-Unix

语法

注释

两个减号是单行注释:

  1. print("Hello World!")--这个是单行注释

多行注释

  1. --[[
  2. print("Hello World!")
  3. print("Hello World!")
  4. --]]

标示符

Lua 表示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。

最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。

Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 W3c 与 w3c 是两个不同的标示符。

关键字

and break do else elseif end false for function if in local nil not or repeat return then true until while

全局变量

在默认情况下,变量总是认为是全局的。

全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。

换句话说, 当且仅当一个变量不等于nil时,这个变量即存在。

数据类型

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 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。

变量

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

Lua 变量有三种类型:全局变量、局部变量、表中的域。

函数外的变量默认为全局变量,除非用 local 显示声明。函数内变量与函数的参数默认为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束(或者是直到下一个同名局部变量的声明)。

变量的默认值均为 nil。

赋值

Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。注意:如果要对多个变量赋值必须依次对每个变量赋值。

a, b = 10, 2x <–> a=10; b=2x

遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:

x, y = y, x – swap ‘x’ for ‘y’

a[i], a[j] = a[j], a[i] – swap ‘a[i]’ for ‘a[i]’

函数调用返回给变量

a, b = f()

f()返回两个值,第一个赋给a,第二个赋给b。

索引

对 table 的索引使用方括号 []

t[i]

t.i – 当索引为字符串类型时的一种简化写法

gettable_event(t,i) – 采用索引访问本质上是一个类似这样的函数调用

循环

  1. while(condition)
  2. do
  3. statements
  4. end
  5. --var从exp1变化到exp2,每次变化以exp3为步长递增var,并执行一次"执行体"。exp3是可选的,如果不指定,默认为1。
  6. for var=exp1,exp2,exp3 do
  7. <执行体>
  8. end
  9. for i=10,1,-1 do
  10. print(i)
  11. end
  12. --打印数组a的所有值 ,i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
  13. for i,v in ipairs(a) do
  14. print(v)
  15. end
  16. repeat
  17. statements
  18. while( condition )

循环控制语句

break 退出当前循环或语句,并开始脚本执行紧接着的语句。

流程控制

控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true 和非nil为真。

要注意的是Lua中 0 为 true

  1. if(布尔表达式)
  2. then
  3. --[ 在布尔表达式为 true 时执行的语句 --]
  4. else
  5. --[ 布尔表达式为 false 时执行该语句块 --]
  6. end

函数

  1. optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
  2. function_body
  3. return result_params_comma_separated
  4. end

optional_function_scope

该参数是可选的制定函数是全局函数还是局部函数,未设置该参数末尾为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。

result_params_comma_separated

函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。

可变参数

  1. function average(...)
  2. result = 0
  3. local arg={...}
  4. for i,v in ipairs(arg) do
  5. result = result + v
  6. end
  7. print("总共传入 " .. #arg .. " 个数")
  8. return result/#arg
  9. end

Lua函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(…) 表示函数有可变的参数。

Lua将函数的参数放在一个叫arg的表中,#arg 表示传入参数的个数。

例如,我们计算几个数的平均值:

print(“平均值为”,average(10,5,3,4,5,6))

运算符

算术运算符

所有语言通用+-*/^%

关系运算符

所有语言通用,特例:不等于为~=

逻辑运算符

设定 A 的值为 true,B 的值为 false:

(A and B) 为 false。

(A or B) 为 true。

not(A and B) 为 true。

其他运算符

.. 连接两个字符串

一元运算符,返回字符串或表的长度。 #”Hello” 返回 5

优先级

从高到低的顺序:

^

not - (unary)

* /

+ -

..

< > <= >= ~= ==

and

or

除了^和..外所有的二元运算符都是左连接的。

字符串

字符串或串(String)是由数字、字母、下划线组成的一串字符。

Lua 语言中字符串可以使用以下三种方式来表示:

  • 单引号间的一串字符。
  • 双引号间的一串字符。
  • [[和]]间的一串字符。
  1. string1 = "Lua"
  2. print("\"字符串 1 是\"",string1)
  3. string2 = ‘w3cschool.cc‘
  4. print("字符串 2 是",string2)
  5. string3 = [["Lua 教程"]]
  6. print("字符串 3 是",string3)

迭代器

泛型 for 迭代器

泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。

  1. for k, v in pairs(t) do
  2. print(k, v)
  3. end

范性for的执行过程:

首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。

第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。

第三,将迭代函数返回的值赋给变量列表。

第四,如果返回的第一个值为nil循环结束,否则执行循环体。

第五,回到第二步再次调用迭代函数

无状态的迭代器

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。

每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。

  1. function square(iteratorMaxCount,currentNumber)
  2. if currentNumber<iteratorMaxCount
  3. then
  4. currentNumber = currentNumber+1
  5. return currentNumber, currentNumber*currentNumber
  6. end
  7. end
  8. for i,n in square,3
  9. do
  10. print(i,n)
  11. end

多状态的迭代器

很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。

  1. array = {"Lua", "Tutorial"}
  2. function elementIterator (collection)
  3. local index = 0
  4. local count = #collection
  5. -- 闭包函数
  6. return function ()
  7. index = index + 1
  8. if index <= count
  9. then
  10. -- 返回迭代器的当前元素
  11. return collection[index]
  12. end
  13. end
  14. end
  15. for element in elementIterator(array)
  16. do
  17. print(element)
  18. end

table

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字、字典等。

Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。

Lua table 是不固定大小的,你可以根据自己需要进行扩容。

Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用”format”来索引table string。

  1. -- 初始化表
  2. mytable = {}
  3. -- 指定值
  4. mytable[1]= "Lua"
  5. -- 移除引用
  6. mytable = nil
  7. -- lua 垃圾回收会释放内存

当我们初始化 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。

模块与包

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

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

  1. -- 文件名为 module.lua
  2. -- 定义一个名为 module 的模块
  3. module = {}
  4. -- 定义一个常量
  5. module.constant = "这是一个常量"
  6. -- 定义一个函数
  7. function module.func1()
  8. io.write("这是一个公有函数!\n")
  9. end
  10. --表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.
  11. local function func2()
  12. print("这是一个私有函数!")
  13. end
  14. function module.func3()
  15. func2()
  16. end
  17. return module

require 函数

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

require(“<模块名>”)或者require “<模块名>”

  1. -- test_module.php 文件
  2. -- module 模块为上文提到到 module.lua
  3. require("module")
  4. print(module.constant)
  5. module.func3()
  6. -- test_module2.php 文件
  7. -- module 模块为上文提到到 module.lua
  8. -- 别名变量 m
  9. local m = require("module")
  10. print(m.constant)
  11. 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库了。

元表(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)。

元方法

  • __index 元方法查看表中元素是否存在,如果不存在,返回结果为nil;如果存在则由__index 返回结果。
  • __newindex 元方法用来对表更新,__index则用来对表访问 。

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

    *__add 键包含在元表中,并进行相加操作。其他还包括__sub __mul __div __mod __unm __concat __eq __lt __le

  • __call 元方法在 Lua 调用一个值时调用。
  • __tostring 元方法用于修改表的输出行为。

协同程序(coroutine)

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

线程和协同程序区别

线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。

在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。

协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

基本语法

方法 描述
coroutine.create() 创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用
coroutine.resume() 重启coroutine,和create配合使用
coroutine.yield() 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果
coroutine.status() 查看coroutine的状态,注:coroutine的状态有三种:dead,suspend,running
coroutine.wrap() 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复
coroutine.running() 返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号

coroutine在底层实现就是一个线程。

当create一个coroutine的时候就是在新线程中注册了一个事件。

当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。

  1. function foo (a)
  2. print("foo 函数输出", a)
  3. return coroutine.yield(2 * a) -- 返回 2*a 的值
  4. end
  5. co = coroutine.create(function (a , b)
  6. print("第一次协同程序执行输出", a, b) -- co-body 1 10
  7. local r = foo(a + 1)
  8. print("第二次协同程序执行输出", r)
  9. local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
  10. print("第三次协同程序执行输出", r, s)
  11. return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
  12. end)
  13. print("main", coroutine.resume(co, 1, 10)) -- true, 4
  14. print("--分割线----")
  15. print("main", coroutine.resume(co, "r")) -- true 11 -9
  16. print("---分割线---")
  17. print("main", coroutine.resume(co, "x", "y")) -- true 10 end
  18. print("---分割线---")
  19. print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
  20. print("---分割线---")
  21. --结果
  22. 第一次协同程序执行输出 1 10
  23. foo 函数输出 2
  24. main true 4
  25. --分割线----
  26. 第二次协同程序执行输出 r
  27. main true 11 -9
  28. ---分割线---
  29. 第三次协同程序执行输出 x y
  30. main true 10 结束协同程序
  31. ---分割线---
  32. main false cannot resume dead coroutine
  33. ---分割线---

resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。

生产者-消费者问题

  1. local newProductor
  2. function productor()
  3. local i = 0
  4. while true do
  5. i = i + 1
  6. send(i) -- 将生产的物品发送给消费者
  7. end
  8. end
  9. function consumer()
  10. while true do
  11. local i = receive() -- 从生产者那里得到物品
  12. print(i)
  13. end
  14. end
  15. function receive()
  16. local status, value = coroutine.resume(newProductor)
  17. return value
  18. end
  19. function send(x)
  20. coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序
  21. end
  22. -- 启动程序
  23. newProductor = coroutine.create(productor)
  24. consumer()

文件I/O

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

  • 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
  • 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法

    简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。

    file = io.open (filename [, mode])

模式 描述
r 以只读方式打开文件,该文件必须存在。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
r+ 以可读写方式打开文件,该文件必须存在。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a+ 与a类似,但此文件可读可写
b 二进制模式,如果文件是二进制文件,可以加上b
+ 号表示对文件既可以读也可以写

简单模式

  1. -- 以只读方式打开文件
  2. file = io.open("test.lua", "r")
  3. -- 设置默认输入文件为 test.lua
  4. io.input(file)
  5. -- 输出文件第一行
  6. print(io.read())
  7. -- 关闭打开的文件
  8. io.close(file)
  9. -- 以附加的方式打开只写文件
  10. file = io.open("test.lua", "a")
  11. -- 设置默认输出文件为 test.lua
  12. io.output(file)
  13. -- 在文件最后一行添加 Lua 注释
  14. io.write("-- test.lua 文件末尾注释")
  15. -- 关闭打开的文件
  16. io.close(file)

完全模式

通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。

  1. -- 以只读方式打开文件
  2. file = io.open("test.lua", "r")
  3. -- 输出文件第一行
  4. print(file:read())
  5. -- 关闭打开的文件
  6. file:close()
  7. -- 以附加的方式打开只写文件
  8. file = io.open("test.lua", "a")
  9. -- 在文件最后一行添加 Lua 注释
  10. file:write("--test")
  11. -- 关闭打开的文件
  12. file:close()

错误处理

任何程序语言中,都需要错误处理。错误类型有:

  • 语法错误,通常是由于对程序的组件(如运算符、表达式)使用不当引起的。
  • 运行错误,程序可以正常执行,但是会输出报错信息。

    我们可以使用两个函数:assert 和 error 来处理错误。

assert

  1. local function add(a,b)
  2. assert(type(a) == "number", "a 不是一个数字")
  3. assert(type(b) == "number", "b 不是一个数字")
  4. return a+b
  5. end
  6. add(10)

实例中assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。

lua: test.lua:3: b 不是一个数字

stack traceback:

[C]: in function ‘assert’

test.lua:3: in local ‘add’

test.lua:6: in main chunk

[C]: in ?

error

error (message [, level])

功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)

通常情况下,error会附加一些错误位置的信息到message头部。

Level参数指示获得错误的位置:

  • Level=1[默认]:为调用error位置(文件+行号)
  • Level=2:指出哪个调用error的函数的函数
  • Level=0:不添加错误位置信息

pcall 和 xpcall、debug

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

> =pcall(function(i) print(i) end, 33)

33

true

> =pcall(function(i) print(i) error(‘error..’) end, 33)

33

false stdin:1: error..

pcall以一种”保护模式”来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。

通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。

Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展看(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。

debug库提供了两个通用的错误处理函数:

  • debug.debug:提供一个Lua提示符,让用户来检查错误的原因
  • debug.traceback:根据调用桟来构建一个扩展的错误消息
  1. xpcall(function(i) print(i) error(‘error..‘) end, function() print(debug.traceback()) end, 33)
  2. --33 stack traceback: stdin:1: in function [C]: in function ‘error‘ stdin:1: in function [C]: in function ‘xpcall‘ stdin:1: in main chunk [C]: in ? false nil
  3. function myfunction ()
  4. n = n/nil
  5. end
  6. function myerrorhandler( err )
  7. print( "ERROR:", err )
  8. end
  9. status = xpcall( myfunction, myerrorhandler )
  10. print( status)
  11. --ERROR: test2.lua:2: attempt to perform arithmetic on global ‘n‘ (a nil value) false
  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 。

    以 ‘(’ (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。

  9. sethook ([thread,] hook, mask [, count]):

    将一个函数作为钩子函数设入。 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义:

    ‘c’: 每当 Lua 调用一个函数时,调用钩子;

    ‘r’: 每当 Lua 从一个函数内返回时,调用钩子;

    ‘l’: 每当 Lua 进入新的一行时,调用钩子。

  10. setlocal ([thread,] level, local, value):

    这个函数将 value 赋给 栈上第 level 层函数的第 local 个局部变量。 如果没有那个变量,函数返回 nil 。 如果 level 越界,抛出一个错误。

  11. setmetatable (value, table):

    将 value 的元表设为 table (可以是 nil)。 返回 value。

  12. setupvalue (f, up, value):

    这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字。

  13. traceback ([thread,] [message [, level]]):

    如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。

调试类型

命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。

图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。

垃圾回收

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 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。
  • collectgarbage(“setstepmul”): 返回 步进倍率 的前一个值。
  • collectgarbage(“step”): 单步运行垃圾收集器。 步长”大小”由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
  • collectgarbage(“stop”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

面向对象

面向对象特征

1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。

2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。

3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

4)抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。

我们知道,对象由属性和方法组成。LUA中最基本的结构是table,所以需要用table来描述对象的属性。

lua中的function可以用来表示方法。那么LUA中的类可以通过table + function模拟出来。

至于继承,可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。

Lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。

  1. -- Meta class
  2. Shape = {area = 0}
  3. -- 基础类方法 new
  4. function Shape:new (o,side)
  5. o = o or {}
  6. setmetatable(o, self)
  7. self.__index = self
  8. side = side or 0
  9. self.area = side*side;
  10. return o
  11. end
  12. -- 基础类方法 printArea
  13. function Shape:printArea ()
  14. print("面积为 ",self.area)
  15. end
  16. -- 创建对象
  17. myshape = Shape:new(nil,10)
  18. myshape:printArea()
  19. Square = Shape:new()
  20. -- 派生类方法 new
  21. function Square:new (o,side)
  22. o = o or Shape:new(o,side)
  23. setmetatable(o, self)
  24. self.__index = self
  25. return o
  26. end
  27. -- 派生类方法 printArea
  28. function Square:printArea ()
  29. print("正方形面积为 ",self.area)
  30. end
  31. -- 创建对象
  32. mysquare = Square:new(nil,10)
  33. mysquare:printArea()
  34. Rectangle = Shape:new()
  35. -- 派生类方法 new
  36. function Rectangle:new (o,length,breadth)
  37. o = o or Shape:new(o)
  38. setmetatable(o, self)
  39. self.__index = self
  40. self.area = length * breadth
  41. return o
  42. end
  43. -- 派生类方法 printArea
  44. function Rectangle:printArea ()
  45. print("矩形面积为 ",self.area)
  46. end
  47. -- 创建对象
  48. myrectangle = Rectangle:new(nil,10,20)
  49. myrectangle:printArea()

数据库访问

LuaSQL。他是开源的,支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL。

  1. require "luasql.mysql"
  2. --创建环境对象
  3. env = luasql.mysql()
  4. --连接数据库
  5. conn = env:connect("数据库名","用户名","密码","IP地址",端口)
  6. --设置数据库的编码格式
  7. conn:execute"SET NAMES UTF8"
  8. --执行数据库操作
  9. cur = conn:execute("select * from role")
  10. row = cur:fetch({},"a")
  11. --文件对象的创建
  12. file = io.open("role.txt","w+");
  13. while row do
  14. var = string.format("%d %s\n", row.id, row.name)
  15. print(var)
  16. file:write(var)
  17. row = cur:fetch(row,"a")
  18. end
  19. file:close() --关闭文件对象
  20. conn:close() --关闭数据库连接
  21. env:close() --关闭数据库环境

参考

菜鸟教程:http://www.runoob.com/lua/lua-tutorial.html

来自为知笔记(Wiz)

时间: 2024-11-03 22:03:47

Lua初探的相关文章

PySide2(PyQt5)根据屏幕大小初始化窗口大小

引言: PySide2获取屏幕分辨率并设置窗口大小 步骤: 1.获取桌面对象desktop,通过桌面对象我们可以获取pc设备的屏幕信息 2.对主window重新设置其宽高 class MainWindow(): def __init__(self): self.window = QtWidgets.QMainWindow() self.initSize(0.6) def initSize(self, rate): desktop = QtWidgets.QApplication.desktop(

PySide2(PyQt5) 实现tab切换及方法详解(不定时更新)

更多分享:www.catbron.cn tab的实现用到的是QTabWidget控件,以下仅做简单的演示 源码: import sys from PySide2 import QtCore, QtGui, QtWidgets class MainWindow(): def __init__(self): self.window = QtWidgets.QMainWindow() self.initSize(0.6) self.mainWidget = QtWidgets.QWidget() se

Lua与C++交互初探之Lua调用C++

Lua与C++交互初探之Lua调用C++ 上一篇我们已经成功将Lua的运行环境搭建了起来,也成功在C++里调用了Lua函数.今天我来讲解一下如何在Lua里调用C++函数. Lua作为一个轻量级脚本语言,他只包含了一些必要的系统库函数,当有需要时还得自己去写.有一次我要做一个两数异或的操作发现函数库里居然没有异或运算.不得不非常苦逼的自己去写.后来接触Lua深了之后才知道将这种"缺陷"可以由C函数来弥补.但要做到这一点对于一个对C只知道if else的学生来说确实还是有不少难度. 在学习

lua与c++交互初探

lua与c++交互初探 自从学习了lua这个脚本语言之后,无时不想着将他与c/c++联系起来,看看他真正的威力.奈何水平有限,网上找的代码无论怎样都无法运行成功.我知道是我少了某一步但是又不知道到底少了什么,于是就在各大博客.网站.论坛不断的摸索测试.我也不知道花了多长时间.总之在今天测试成功了.我把我测试遇到的问题和解决过程贴出来供大家参考. 一.lua环境的搭建 建议去网上下载luaforwindow,这是一款跟众多window系统的软件一样,安装起来简单方便,一路点next就能搞定了.而且

Lua学习笔记--函数初探

感觉学习语言的话,函数是个重头戏.来看一下Lua的函数是神马样纸的东东! 一.简单的函数例子 --一个简单的函数:阶乘 function factorial(num) if num == 0 then return 1 else return num * factorial(num - 1) end end 恩,这就是个函数.function关键字,说明这是个函数,然后是函数名,后面跟的是参数列表,使用括号括起来.函数的结尾需要一个end关键字,表明函数结束了. 下面看以下怎么调用: --一个简

lua MVC框架 Orbit初探

介绍 http://keplerproject.github.io/orbit/ Orbit是lua语言版本的MVC框架. 此框架完全抛弃CGILUA的脚本模型, 支持的应用, 每个应用可以卸载一个单独的文件中,当然你也可以将它拆为一个文件, 当你需要时候. 此框架运行在WSAPI协议的服务器上,所以可以工作在 Xavante和一些CGI和fastcgi程序上. Orbit is an MVC web framework for Lua. The design is inspired by li

【笨木头Lua专栏】基础补充04:迭代器初探

今天学习的内容还蛮有意思的,让我兴奋了一下~ 笨木头花心贡献,哈?花心?不,是用心~ 转载请注明,原文地址: http://www.benmutou.com/archives/1714 文章来源:笨木头与游戏开发 1.迭代器 什么是迭代器?别傻了,我最讨厌的就是名词解释了,反正就是用来遍历集合的一种方式. 比如,我们最常用的pairs,如下代码: local t = {"fdsd", "445"}; for k, v in pairs(t) do print(&qu

Lua学习笔记--Lua调用C初探

上次学习了怎么用C调用Lua的函数,并返回一个结果,这次看看怎么反过来,用Lua调用C的函数. 一.简介 C调用Lua函数比较简单,只需要操作相关的栈就可以了,但是Lua调用C的话,稍微有一点麻烦,虽然还是用栈来进行数据的传递,但是由于Lua中本身没有C中写的函数,所以需要多一步将C函数注册到Lua中的步骤. Lua反过来调用C函数的话,首先,我们要写一个要被调用的函数,这个函数有一个格式的要求 ,返回值为int,但是这个int并不代表Lua函数的返回值,而是函数返回值的个数,Lua支持多重返回

【笨木头Lua专栏】基础补充07:协同程序初探

哎.周五晚上我都还这么努力看书.真是好孩子.(小若:不想吐槽了) 事实上我都准备rs=1&u=http%3A%2F%2Fwww%2Ebenmutou%2Ecom%2Farchives%2F1733&p=baidu&c=news&n=10&t=tpclicked3_hc&q=25013069_cpr&k=%CD%E6%D3%CE%CF%B7&k0=%B4%F2%D3%A1&kdi0=1&k1=%D7%C0%D7%D3&k