首页 > 其他 > 详细

Lua总结一

时间:2015-02-28 23:07:07      阅读:695      评论:0      收藏:0      [点我收藏+]

值和类型(Values and Types)

Lua是一门动态类型语言,这意味着变量没有类型,只有值有类型。语言没有类型定义,所有值携带自己的类型。
Lua中所有的值都是一等公民,这意味着所有的值都可以存储在变量中,作为参数传递给其他函数,作为函数的结果返回。
值得注意的是这点对函数也成立,在Java中函数是没这个待遇的。比如对一个列表排序,需要一个比较函数,Lua可以直接传递比较函数,而Java需要为这个比较函数创建一个类型,然后传递这个类型的实例。
来看一个例子,将一个列表从大到小排列:
Lua

list = {1, 2, 3, 4, 5}

function comp(i, j)
    return i > j
end

table.sort(list, comp)

Java

List<Integer> list = new ArrayList<Integer>(Arrays.asList(new Integer[] { 1, 2, 3, 4, 5 }));

Comparator<Integer> comp = new Comparator<Integer>()
{
    @Override
    public int compare(Integer o1, Integer o2)
    {
        return o1 < o2 ? 1 : -1;
    }
};

Collections.sort(list, comp);

Lua提供了八种基础数据类型:nil、boolean、number、string、function、userdata、thread、table。

nil:表示空。

boolean:布尔类型,只有nil和false表示false,其他都为true。0和1都表示true。

number:数值类型,两种内部表示,integer和float,默认为64位整型和双精度浮点型。

string:字符串类型,值是不可变的,每次修改都会创建一个新的string对象。同Java的StringBuilder一样,Lua提供了table.concat来拼接字符串。

function:函数类型。他是一等公民。

userdata:允许C中的数据保存在Lua中。这有什么作用呢?Lua作为一种嵌入式语言,自身的API是很少的,但是可以很方便的通过C来扩展功能。比如为Lua提供socket功能,使它能够像如下方式使用:

connection = socket.connect(host, port)
connection.write(msg)

这个connection是一个连接对象,它是由CAPI创建的一个userdata,可以在Lua中使用。

thread:独立的执行线程,用于实现协程。Lua的thread不同于操作系统的thread,即时操作系统不支持thread,Lua能在这种操作系统上支持协程。(协程后面介绍)

table:表,Lua仅有的数据结构。不过可以用于表示数组,集合,链表,树,图等数据结构。

环境与全局环境(Environmets and the Global Environment)

当引用一个未被声明的变量var,句法上表示_ENV.var。_ENV的值是一个table,这个值被称为环境。Lua中每个被编译的chunk都有一个额外的local变量,称为_ENV。 当访问一个非local变量,会从_ENV中查找,当定义一个非local变量,会存储在_ENV中。
Lua中有个一个特殊的环境叫做全局环境(_G),_ENV的默认值就是全局环境。所以当你定义一个非local的变量,默认为全局变量。
如果我们定义自己的环境_ENV,那么非local变量就会存储在自定义的环境中,来看一个例子(lua5.2以上版本):

employee1 = {_G = _G}  -->保留全局环境的引用
_ENV = employee1
id = 1              -->不会作为全局变量
name = "xiaoming"
salary = 3000

_ENV = _G 
employee2 = {_G = _G}
_ENV = employee2
id = 2              
name = "daming"
salary = 20000

_ENV = _G 
print(employee1.id, employee1.name, employee1.salary)
print(employee2.id, employee2.name, employee2.salary)

错误处理(Error Handing)

Lua作为一门嵌入式扩展语言,所有的行为都是从宿主程序的一次C函数调用开始的。当Lua编译或者运行发生错误时,会把控制权交给宿主程序,由宿主程序处理错误。
Lua可以通过error函数来抛出一个错误。如果Lua想捕获错误,则需要在保护模式下运行代码,即通过pcall或xpcall来执行方法。
当使用xpcall时,需要提供一个错误处理函数,他可以在错误发生且栈还未展开时执行。如果栈还未展开,这意味你可以获得调用上下文的所有信息,比如当前环境中的一个本地变量。不过不能错误处理函数中直接使用上下文中信息,需要借助debug库,来看一个例子:

function get1()
    local i = 1
    get2()
end

function get2()
    local j = 2 
    get3()
end

function get3()
    local k = 3
    error("throw error")
end

function handler(msg)
    --print(debug.traceback())            -->通过打印栈回溯信息确定方法所在的level
    name, value = debug.getlocal(3, 1)     -->获取get3方法的本地变量k
    print(name, value)
    name, value = debug.getlocal(4, 1)     -->获取get2方法的本地变量j
    print(name, value)
    name, value = debug.getlocal(5, 1)     -->获取get1方法的本地变量i
    print(name, value)
    return "warp "..msg
end

xpcall(get1, handler)

输出:

k   3
j   2
i   1

元表和元方法(Metatables and Metamethods)

Lua为每一个值都提供了元表,元表就是一个table,它定义了原始值在特定操作下的行为。行为由方法提供,这种方法称为元方法。元表可以控制很多操作,比如加法,取长度,比较,索引等等。
table和userdata类型的值有独立的元表,其他类型的值分别共享一张元表。
比如我们的变量可以存储在三个地方,global域,session域,page域,查找顺序为page,session,global。用元表可以这样来实现:

global = {a = 1}
session = {b = 2}
session_mt = {__index = global}     -->定义元表session_mt和元方法__index
setmetatable(session, session_mt)   -->session中查找不到的key会去global中查找
page = {c = 3}
page_mt = {__index = session}
setmetatable(page, page_mt)

print(page.c, page.b, page.a)

输出:

3   2   1

协程(Coroutines)

Lua中的协程又叫协同式多线程,协程之间不会抢占执行,只有在协程内部调用yield才会让出执行权。
Lua中的每个协程都是独立的执行序列,协程之间不共享内存。
Lua协程很多特点,比如yield之后调用resume继续执行,会从上次yield的地方继续执行,并且可以访问到yield之前的那些局部变量。

闭包(Closure)

当函数A调用函数B时,函数B可以操作函数A中的局部变量,这种形式叫做闭包。看一个例子:

function retfoo()
    local i = 0
    return function ()
        i = i + 1
        return i
    end
end

foo = retfoo()
print(foo(), foo())

输出:

1   2

闭包的作用在于可以保存状态,让多次调用之间产生关系。

For语句

Lua中提供了两种形式的for语句,numeric for 和 generic for。

  1. numeric for 的语法如下
stat ::= for Name ‘=’ exp ‘,’ exp [ ‘,’ exp ] do block end 

block把Name作为循环变量,起始值为第一个exp,直到第二个exp为止,步长为第三个exp。

一个numeric for像这样:

for v = e1, e2, e3 do block end

它等价于下面的代码

do
    local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
    if not (var and limit and step) then error() end
    var = var - step
    while true do
        var = var + step
        if (step >= 0 and var > limit) or (step < 0 and var < limit) then
            break
        end
        local v = var
        block
    end
end
  1. generic for 的语法如下
stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}

一个generic for像这样:

for var_1, … , var_n in explist do block end

它等价于下面的代码

do
    local f, s, var = explist
    while true do
        local var_1, … , var_n = f(s, var)
        if var_1 == nil then break end
        var = var_1
        block
    end
end

f, s, var 可以看作迭代函数,迭代不变量,迭代变量。
如此Lua的迭代器可以分成两种,一种是有状态的,一种是无状态的。

有状态的迭代器采用闭包来实现,比如编写一个迭代器来遍历table数组。

function ipairs(table)      -->有状态的迭代器
    i = 0 
    return function()
        i = i + 1
        if i > #table then return nil end
        return i, table[i]
    end
end

table = {"a", "b", "c", "d", "e"}
for i, v in ipairs(table) do
    print(i, v)
end

有状态的迭代器的缺点在于每遍历一次都需要创建一个迭代器,无状态的迭代器则不需要。

function ipairs0(table, i)   -->无状态的迭代器
    i = i + 1
    local v = table[i]
    if not v then return nil end
    return i, v
end

function ipairs(table)
    return ipairs0, table, 0
end

table = {"a", "b", "c", "d", "e"}
for i, v in ipairs(table) do
    print(i, v)
end

在这个例子中,ipairs0, table, i 分别对应f, s, var。在上下文中,i既是迭代变量,也是table数组的当前索引。

模块与包(Module and Package)

从用户的观点来看,一个模块就是一个程序库,可以通过require来加载。然后便得到一个全局变量,表示一个table。这个table就像一个名称空间,其内容就是模块中导出的所有东西,如函数和常量。

加载一个模块

require “mod”
mod.foo()

编写一个模块
把模块中要导出的内容放在一个table中,用模块名作为全局变量引用这个table,模块的最后返回这个table。

modulename = …       -->模块名由require函数传入
local moduletable = {}   -->模块table,存放要导出的内容
_G[modulename] = moduletable    -->用模块名作为全局变量引用模块table

-- 定义模块中要导出的函数和常量

return moduletable     -->相当于package.loaded[modulename] = moduletable

如果一个模块被加载过,require函数会返回已经加载的模块。如果模块需要热更新,可以这样实现:

package.loaded[modulename] = nil
require “modulename”

当然在实际的代码中,还有更多的问题需要考虑,比如闭包中的upvalue的更新。

面向对象

一个对象要有变量和方法,Lua可以用table来表示对象。

Girl = {age = 18}
function Girl.grow(self, v)
    self.age = self.age + v 
end

Girl.grow(Girl, 1)
print(Girl.age)

Girl是一个对象,age是Girl的变量,grow是Girl的方法。grow方法第一个参数为self,示例中Girl.grow(Girl, 1)调用grow方法时,传入了Girl。这个调用存在另外一种语法糖:

Girl:grow(1)

grow方法可以这样来定义:

function Girl:grow(v)
    self.age = self.age + v 
end

在Java、C#这样的面向对象语言中,有类和对象的区分。类是对象的抽象,对象通过类来创建,比如 Girl g = new Girl()。Lua中没有类的概念,如果需要表达类,可以采用原型语言的做法。对象a拥有一个原型b,对象a会在原型b上查找不存在的操作,原型b也是一个普通对象。

function Girl:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function Girl:grow(v)
    self.age = self.age + v
end

g = Girl:new({age = 18})
g:grow(1)
print(g.age)

利用元表,Lua也可以表达出继承,多态的概念。不过我觉得Lua拥有更灵活的语法,没必要像面向对象那样死板的编程。比如如果有girl1和girl2两个对象,它们的grow行为不同,那么在面向对象中需要创建Girl1和Girl2两个类,而在Lua中只需要简单的修改girl2的grow方法:

local oldGrow = g.grow
function g:grow(v)
    oldGrow(g, v)
    print("do other things...")
end

g:grow(1)
print(g.age)

参考资料
《Lua程序设计》第二版
Lua 5.3参考手册
Lua 5.3云风翻译版

Lua总结一

原文:http://blog.csdn.net/darwinchina/article/details/43990505

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!