Lua总结
- C语言做得很好的领域:性能、底层操作以及与第三方软件的接口。
- Lua语言提供C不善于的:高级语言、动态结构、简洁、易于测试和调试等。具有良好的安全保证,自动内存管理,简便的字符串处理功能及其它动态数据的改变。
- Lua语言特征:
- 可扩展性:可通过Lua代码或C代码扩展,很多功能都是通过外部库来扩展的。
- 简单:小巧,内容少但功能强大
- 高效率:目前平均效率最高的脚本语言
- 与平台无关:可以运行任何平台,不是通过使用条件编译实现,而是完全使用ANST(ISO) C,意味着只要用ANST C编译器就可以编译并使用Lua
- Lua的长处之一就是可以通过新类型和函数来扩展其功能。动态类型检查最大限度允许多态出现,并自动简化调用内存管理的接口,因为这样不需要关心谁来分配内存谁来释放内存,也不必担心数据溢出。高级函数和匿名函数均可以接受高级参数,使函数更为通用。
- Lua可以调用lua或者C实现的函数,Lua所有标准库都是用C实现的。标准库包括String库、table库、I/O库、OS库、算术库、debug库。
- userdata可以将C数据存放在Lua变量中,userdata在Lua中除了赋值和相等比较外没有预定义的操作。userdata用来描述应用程序或者使用C实现的库创建的新类型。
uppack函数多值返回特殊函数,接受一个数组作为输入参数,返回数组的所有元素。uppack被用来实现范型调用机制。用递归循环参数个数返回。
function unpack(t, i)
i = i or 1
if t[i] then
return t[i], unpack(t, i + 1)
end
end
Lua可接受可变数目的参数,使用三点(...)表示函数有可变的参数,Lua将函数的参数放在一个叫arg的表中,arg表中还有一个域n表示参数的个数。
g(3) a=3, b=nil, arg={n=0}
g(3, 4) a=3, b=4, arg={n=0}
g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2}
- Lua中的函数是带有词法定界的第一类值。
- 第一类值指:在Lua中函数和其它值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
- 词法定界指:嵌套的函数可以访问他外部函数中的变量。
比如print,实际上是说一个指向函数的变量,像持有其它类型值的变量一样。
a = {p = print}
a.p("Hello World") --> Hello World
既然函数是值,那么表达式也可以创建函数了,Lua中我们经常这样写:
function foo (x) return 2*x end
这实际上是Lua语法的特例,下面是原本的函数:
foo = function (x) return 2*x end
函数定义实际上是一个赋值语句,将类型为function的变量赋给一个变量。我们使用function (x) ... end来定义一个函数和使用{}创建一个表一样。
- 闭包:是一个函数以及它的upvalue.
- 词法定界指:当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部函数的局部变量。
upvalue:sortbygrade函数内部的sort中的匿名函数可以访问sortbygrade的参数grades,在匿名函数内部grades不是全局变量也不是局部变量,我们称为外部的局部变量(external local variable)或者upvalue。
function sortbygrade (names, grades)
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2] -- compare the grades
end)
end
c1、c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。
function newCounter()
local i = 0
return function() -- anonymous function
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
- 闭包在上下文环境中提供很有用的功能,如作为函数嵌套的函数(newCounter)。这一机制使得可以在Lua的函数世界组合出奇幻的编程技术。闭包也可用在回调函数中,Button实际上是一个闭包,因为访问upvalue digit,digitButton完成任务返回后,局部变量digit超出范围,回调函数仍然可以被调用并且可以访问局部变量digit.
function digitButton (digit)
return Button{ label = digit,
action = function ()
add_to_display(digit)
end
}
end
- 闭包在不同上下文也有用途,因为函数被存储在普通的变量内可以方便看定义或者预定义函数。通常需要原始函数有一个新的实现时可以重定义函数。比如可以使用闭包重定义io库的open函数来限制程序打开的文件。
oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end
- 尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。
function f(x)
return g(x)
end
g的调用是尾调用。可以将尾调用理解成一种goto,在状态机的编程领域尾调用是非常有用的。
- 迭代器:是一种支持指针类型的结构,可以遍历集合的每一个元素。可以使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。需要保留上一次成功调用的状态和下一次成功调用的状态,也就是知道来自于哪里和将要前往哪里。闭包提供的机制可以容易实现这个任务。记住:闭包是一个内部函数,可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。所以一个典型的闭包结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。
- 编译、运行、错误信息:虽然我们把Lua当作解释型语言,但是Lua会首先把代码预编译成中间码然后再执行(很多解释型语言都是这么做的)。在解释型语言中存在编译阶段听起来不合适,然而,解释型语言的特征不在于他们是否被编译,而是编译器是语言运行时的一部分,所以,执行编译产生的中间码速度会更快。我们可以说函数dofile的存在就是说明可以将Lua作为一种解释型语言被调用。
dofile,把它当作Lua运行代码的chunk的一种原始的操作。dofile实际上是一个辅助的函数。真正完成功能的函数是loadfile;与dofile不同的是loadfile编译代码成中间码并且返回编译后的chunk作为一个函数,而不执行代码;另外loadfile不会抛出错误信息而是返回错误码。完成简单的功能dofile比较方便,他读入文件编译并且执行。然而loadfile更加灵活。在发生错误的情况下,loadfile返回nil和错误信息,这样我们就可以自定义错误处理。另外,如果我们运行一个文件多次的话,loadfile只需要编译一次,但可多次运行。dofile却每次都要编译。
require:Lua提供高级的require函数来加载运行库。粗略的说require和dofile完成同样的功能但有两点不同:
1. require会搜索目录加载文件
2. require会判断是否文件已经加载避免重复加载同一文件。由于上述特征,require在Lua中是加载库的更好的函数。
- 协同程序:非抢占式的多线程,可以处理一边读一边写。每一个协同等同于一个线程,yield-resume可以实现在线程中切换。然后与真正的多线程不同的是协同是非抢占式的。
对非抢占式多线程来说,只要有一个线程调用一个阻塞操作(blocking operation),整个程序在阻塞操作完成之前都将停止。
- 数据结构:table是Lua中唯一的数据结构,在C语言经常使用(arrayst和lists)来实现大部分的数据结构。
- 数组:初始化数组的时候间接定义了数组的大小,数组大小不固定,可动态增长。
Metatables and Metamethods:任何一个表可以是其它一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)。元表作用:可以重定义新的运算。
Metamethod原则:如果第一个参数存在带有_add域的metatable,Lua使用它作为metamethod,和第二个参数无关;
- 面向对象:继承使得类可以访问其它类的方法。
Lua中的对象不是元生的,所以有多种方法可以实现面向对象程序设计。index和metamethod是简洁、性能、灵活综合最好的。
实现的关键在于:将函数用作_index,记住一个表的metatable存在一个_index函数时,如果Lua调用一个原始表中不存在的函数,Lua将调用这个_index指定的函数,这样可以用_index实现多个父类中查找子类不存在的域。
多重继承意味一个类拥有多个父类,所以不能创建一个类的方法去创建子类。而是定义一个特殊的函数createClass来完成这个功能,将被创建的新类的父类作为这个函数的参数。