chrisxie的专栏
2008-09-30 12:09
Lua 对于国人来说可能还比较陌生,然而随着它在电子娱乐、网络娱乐界得到大量的应用,许多人也开始关注起它来。
Lua 虽然是一门脚本语言,但麻雀虽小,五脏却俱全。Lua 主要是面向过程的语言,与大多数脚本语言无异;但 Lua 围绕“栈”的语言构造,强大的表格基本类型,不仅让它身手敏捷(所有优秀脚本语言中速度最快的),而且拥有了百变金身(语法优美,灵活多变),如果说一些语言标榜的诸多特性,例如面向对象,对于 Lua 来说,居然只要用点“语法糖”就能搞定,你是不是觉得有点夸张了?
夸张的事情在 Lua 中可不止这一件,关键的关键是,Lua 实现它们都不费吹灰之力,也不用艰深的语法,这才是所有夸张中最夸张的。那么,如果你也开始觉察到 Lua 的不普通的话,就请随我一同走进 Lua 的世界。
新手提示: 可以下载最新的 LuaEdit 这个程序,来编辑和运行 Lua 脚本。你可以很容易的从网上找到并下载它,3.0或以上版本就足够运行下面的示例了。打开 LuaEdit,File-New Unit,输入或粘贴代码,按“播放”键即可。
Lua 之简
Lua 给我们的东西,或者说需要我们记住的东西,只有变量、关键字和基本语法。而关键字和语法又与常见语言几乎一样,像if…then…elseif…else…end, while…do, for…in, function, repeat(循环次数控制), return; 像 +、-、*、/、^ 之类的标准数学符号 — 这些对于编程有点基础的人来说,都几乎免学习。所谓“变量”的东西除了能装单个的数、字符串、表(table)、函数(function)以外,就是所谓的自定义数据(userdata)了,这个 userdata 理论上可以放任何东西,但如果 Lua 没有一个宿主的话,它似乎也不会被用到,所以无视它。
总之,Lua 给我们的一切,用一页纸几乎可以全部写出来(参见 Lua 5.1 白皮书索引前一页),而标准库则更是简到了“精致”的地步(可以参见本 Blog 的 Lua 基础篇转载)。
超出这一页,那么请发挥自己的想象力吧!
函数语法糖
在 Lua 编程者看来,Lua 的函数就是一种特殊的变量,用 function 声明它,用 () 紧跟在变量名后来使用它,像这样:
01. f = function (x,y)
02. return x + y
03. end
04. print(f(1,1))– 注释:print 全局函数用于在控制台打印文字,此处输出 “2” 当然,对于一个习惯了C,Java等语言的编程者来说,会感觉上述语法有些怪异,有点北方人碰上个四川人讲话的感觉 — 明白但拗口。Lua 不希望别人叫它拗口的语言(尽管 Lua 这个词是个西班牙语词,意思为月亮),于是为众多不同口味的人准备了语法糖,来个 VB/Delphi 款式怎么样?
01. function f(x,y)
02. return x + y
03. end
04. print(f( 1 , 1 )) — 输出“2”
嵌套函数
既然 Lua 的函数本质上就是一个普通变量,那么我们想在哪里用它都行,即使是在一个函数里面来使用函数!
01. function f(x,y)
02. function f1(x)
03. return x = x + 1
04. end
05. return f1(x)+y
06. end
07. print( 1 , 1 ) — 显示” 3 ” 这被称为“内嵌函数”,并且也不是 Lua 的专利,Javascript 也具备这种能力。
多返回值函数和无限形参
如果说这有些让你不太舒适,那么函数的如下功能,应当会让你略感顺心如意,请看多返回值函数:
01. function f(x)
02. return x, x+ 1 , x+ 2
03. end
04. y,y1,y2=f( 1 )
05. print(y,y1,y2) — 打印“ 1 2 3”
要接收3个参数啊,麻烦,我只想接收一个:那么不接收就行了:
01. function f(x)
02. return x, x+1, x+2
03. end
04. y = f(1)
05. print(y) — “1” 不不,我只要接收第2个参数:。。。那你把第一个参数一“扛”了之:
01. function f(x)
02. return x, x+1, x+2
03. end
04. _, y1 = f(1)
05. print(y1) — “2”
有难以计数的参数准备传入到函数?传入参数少了一项??忘记了传入参数???很好,Lua 其实就是为那些不喜欢事先精确设计每一件事情的人而设计的,一切尽在“ … ”中:
01. function f(…)
02. for _, x in ipairs(arg) do
03. print(x)
04. end
05. end
06. f( 4 , 5 , 6 )
07. –打印出:
08. — 4
09. — 5
10. — 6
11.
12. Lua 怎么做到的?答案得归功于 Lua 栈式的内部存储结构和典型容器:表。
放在函数里的 OOP
下面,我们来挑战下函数的极限:想用函数来模拟面向对象编程(OOP) 吗? 这个 Lua 函数实现的“狼吃兔子”游戏:
01. function CreateAnimal(species,food,hungry,speed,living) — 初始化动物类函数,参数依次为 种类、食物、饥饿度、速度、存活标记
02. local function getSpecies() return species end
03. local function getHungry() return hungry end
04. local function getSpeed() return speed end
05. local function getFood() return food end
06. local function getLiving() return living end
07. local function setLiving(bLive) living =bLive end — 设置为 false ,表示该动物已死亡
08. local function eat(otherAnimal) — 如果觅食主体的食物就是客体的种类,并且主体饥饿度大于 0 ,并且主体的奔跑速度大于客体,则吃下客体,客体死亡,主体饥饿度下降 1
09. if (food == otherAnimal . getSpecies() and hungry > 0 and speed > otherAnimal . getSpeed()) then
10. hungry = hungry – 1
11. otherAnimal . setLiving( false )
12. end
13. end
14. return {getSpecies = getSpecies,getLiving = getLiving,setLiving = setLiving,getSpeed = getSpeed,getFood = getFood,getHungry = getHungry,eat= eat}
15. end
16.
17. rabbit = CreateAnimal(“rabbit”,”grass”, 1 , 10 , true ) –新建一个动物:兔子
18. wolf = CreateAnimal(“wolf”,”rabbit”, 2 , 20 , true ) –新建一个动物:狼
19. print(rabbit . getLiving(),wolf . getHungry()) — true , 2
20. wolf . eat(rabbit) –狼吃兔子
21. print(rabbit . getLiving(),wolf . getHungry()) — false , 1
22.
23.
function 不经意间变成了几乎与 class相媲美的存在,要不是因为必须为每一个想输出的属性都得做一个 Get,Set这样的函数麻烦,function 还真成了香饽饽了。
要想解释清楚上述代码的工作原理,着实不容易,得用到“闭包”之类比较深奥的概念。不过,如果只是简单的假设函数传入过来的形参也就是一些普通的本地变量的话(local variables),因为使用这些变量的函数还存在着(被 rabbit、wolf 所引用),所以这些变量也未被垃圾回收,从而发挥了私有数据(从某种意义上来说还是这几个函数的共享数据)的作用。
匿名函数
“闭包”也有实际的用途,下面这个函数返回了一个匿名函数,作为计数器使用。
01. function createCounter()
02. local counter = 0
03. return function()
04. counter = counter + 1
05. return counter
06. end
07. end
08. f = createCounter()
09. print(f())
10. print(f())
11.
12. — 输出
13. — 1
14. — 2
table-类的原型!
有人说 table (表类型,Lua 里,“ v = {} ”就建立了一张空表)就相当于一个数组,一个C的结构体变量,一个 Python 的 List+Dictionary,或者一个Javascript 的 Object。其实都是,也都不是。数组不能有负数作为下标;结构体一旦生成,就不能往里面添加数据或函数成员;Object可不是默认就支持 [] 的寻址操作的。下面就是一个比较夸张的 table 的例子:
01. Mary = {name= ‘Mary’ ,
02. title= ‘Miss.’ ,
03. age=28,
04. friends={ ‘Mike’ , ‘John’ },
05. getAge= function (self,asker)
06. r = table.foreach(self.friends, function (i,v) if (asker.name== v) then return v end end)
07. if (r ~= nil) then
08. return self.age
09. else
10. return nil
11. end
12. end
13. }
14. Mike = {name = ‘Mike’ }
15. print(Mary:getAge(Mike)) — 输出 “28”
16. Tom = {name = ‘Tom’ }
17. print(Mary:getAge(Tom)) — 输出 “nil”
18.
这个例子表现的是 Mary 小姐的一个忌讳,Mary (Mary 变量代表)有两个朋友:Mike 和 John,Mary 对别人询问她的年龄特别敏感,除非是她的朋友,否则她会闭口不答。因此,当传入 Mike 给 getAge 的时候,获得“28”岁的回答,而 Tom 的鲁莽询问则惨遭无视。从这个例子可以看到,表还是原来的表,但可以装载子表 friends,也可以装载函数 getAge。table 已经具备了完整对象容器的两大基本功能:存放数据和成员函数。
新语法糖: 15行的 Mary:getAge(Mike),它与 Mary.getAge(Mary,Mike) 完全等价,让习惯 OOP 的人能找到 OOP 的感觉。
操作符重载
这项功能一直是 C++ 引以为豪的特性,C# 因为继承了这一特性也增色不少。尽管它并不是一项非常重要的特性,但它使得类看上去更像是一个数据类型,从而让 C++ 得以部分保持了 C 简洁的风格。Lua 本身是函数式的语言,但借助 metatable (元表)这个强大的工具,Lua 实现操作符重载易如反掌。table 和 userdata 两种数据类型才带有 metatable 功能。下面这个例子用表类型模拟复数,x表示实部,y表示虚部;然后把复数相加的逻辑(实部+实部,虚部+虚部)写进元表内 __add 方法中,因此,(1+1i) + (3+4i) = (4+5i)。元表是普通表变量中的一张隐藏的表,用 setmetatable 函数设置它,想要读出来则用 getmetatable。
01. meta={__add=function(op1,op2)
02. op = {}
03. op.x = op1.x + op2.x
04. op.y = op1.y + op2.y
05. return op
06. end
07. }
08. a={x=1,y=1}
09. setmetatable(a,meta)
10. b={x=3,y=4}
11. c = a + b
12. print(c.x, c.y) — 输出 4,5 建立自己的类
好了,我们承认 table 是强大的。然而 table 每次产生都要从 “{}”开始,也就是从零开始。我们需要自己的类型,在OOP时代,或者说,我们需要自己的类。在建立自己的类前,让我们深入一下 metatable 这个 Lua 的“百宝箱”。
metatable 虽然是张普通的表,但却是Lua 内部系统直接认识的一张表,就像上例,Lua 解析 + 运算符,却从相关操作数 a (或者 b)的元表中查找 __add 字段(术语叫“事件”)来寻求处理方法(因为 Lua 自己不知道如何把两张表相加)。这类方法被称为“元方法”(metamethod)。
除了与操作符重载有关的元方法以外,要建立自己的类,我们不得不认识另外一个元方法,它就是大名鼎鼎的“__index”。当表格搜寻成员未果的时候,Lua 会触发它,__index 所指向的元方法,元方法所返回的结果就成为了搜寻结果。__index元方法还可以直接为一张表(细数起来,这可是张表中表中表了。。。)。
说了这么多,还是让我们用实例来看一下吧。接上例,如果我们正在编制一个复数计算程序,需要定义许多复数,那么能实现一个“永久”的复数类型 (complex)那是再好不过了。你可以这样写 Lua:
01. complex={__add=function(op1,op2)
02. op = {}
03. op.x = op1.x + op2.x
04. op.y = op1.y + op2.y
05. setmetatable(op,complex)
06. return op
07. end ,
08.
09. create=function(o)
10. o = o or {}
11. setmetatable(o,complex)
12. return o
13. end
14. }
15. a=complex.create{x=1,y=1}
16. b=complex.create{x=3,y=4}
17. c = a + b
18. print(c.x, c.y) — 输出 4,5 在该代码中,利用 complex.create 创建复数,只需要输入复数的实部和虚部的值即可,然后可以对复数使用加法运算,加法运算的结果(这里指 c)也是复数。我们这里说一个表是复数(表),不仅仅要求该表具有 x,y 两项数值,而且要具有复数的行为,类似“相加”、“相减”、“相乘”。。。等等。
新语法糖: 在给函数传递参数的时候,func({1,2,3}) = func{1,2,3}
实现类的单继承
一些人可能比较羡慕一些具有完整OOP 语法的脚本语言,象 Ruby。Lua 尽管被设计用来进行函数式的编程,但要想支持 OOP 语法却也不难,仅用上面已经提到的关于 metatable 即元表的相关知识就能达到。网上已经有不少关于 Lua 实现继承的例子,笔者又进行了自己的包装,使得运用起来更加自然易懂。
我们建立了如下的 Lua 类继承的语法。
1. 一切都继承自 object
2. 要直接从 object 产生新类,使用语句: NewClass = object(),相当于 Ruby 的 NewClass < object
3. 要从 NewClass 产生派生类,使用语句: SubClass = NewClass()
4. 要为某个类,如 SubClass 书写构造函数,使用语法: function SubClass:constructor(p1,p2,...) functionbody end
5. 要为某个类新建一个函数,使用语法: function SubClass:func(p1,p2,...) functionbody end
6. 要按照 Java 的样式定义一个新类,按照如下格式书写:
SubClass = NewClass{
data1 = 1,
data2 = 2,
function constructor( self , p1, p2)
...
end ,
function func( self , p1, p2)
...
end
}
7. 要新建一个类的实例,相当于 Java中的 Class1 c = new Class1(),可以这样书写:c = new(Class1)
8. 要调用带参数的构造函数,相当于 Java 中的 Class1 c = new Class1(p1),可以这样书写:c = new(Class1,p1)
9. 你也可以利用表直接初始化新的类实例:c = new(Class1, {newvalue=9} )
为了实现这样比较简洁的语法,我们只需要实现根类 object 和具有语法糖作用的 new 函数就行了,下面是实现代码:
01. object = {
02.
03. }
04.
05. setmetatable(object, {__call =
06. function ( super ,init)
07. local function createClass( super ,init)
08. local class = init or {};
09. class . super = super
10. class .constructor= false
11. class .instance= function (self, o, ...)
12. local obj={}
13. --copy from self( class prototype)
14. for k,v in pairs(self) do
15. obj[k] = v;
16. end
17. if (type(o) == "table" ) then
18. --copy from o
19. for k,v in pairs(o) do
20. obj[k] = v;
21. end
22. else
23. table.insert(arg, 1, o);
24. end
25.
26. do
27. local function call_constructor(c,...)
28. if c. super then
29. call_constructor(c. super ,...)
30. end
31. if c.constructor then
32. c.constructor(obj,...)
33. end
34. end
35. call_constructor( class ,unpack(arg))
36. end
37. setmetatable(obj,{ __index=self. super })
38. return obj
39. end
40.
41.
42. setmetatable( class ,{
43. __call = createClass,
44. __index = class . super
45. })
46.
47. return class
48. end;
49.
50. return createClass( super ,init);
51. end;
52. });
53.
54. function new ( class , init, ...)
55. return class :instance(init, ...)
56. end 这里用到了 __call 元方法,用来自定义“调用”操作时表的行为。
测试我们建立的继承语法的代码及输出如下(假设定义 object 的代码放在 inheritance.lua 文件中):
01. dofile( "inheritance.lua" ) -- 定义继承树的根类 object
02.
03. classA = object(); -- classA 从 object 派生
04.
05. classB = classA(); -- classB 从 classA 派生
06.
07. function classA:constructor(x) -- 定义 classA 的构造函数
08. print( "classA constructor" )
09. self.x=x
10. end
11.
12. function classA:print_x() -- 定义一个成员函数 classA:print_x
13. print(self.x)
14. end
15.
16. function classA:hello() -- 定义另一个成员函数 classA:hello
17. print( "hello classA" )
18. end
19.
20. function classB:constructor() -- 定义 classB 的构造函数
21. print( "classB constructor" )
22. end
23.
24. function classB:hello() -- 重载 classA:hello 为 classB:hello
25. print( "hello classB" )
26. self. super .hello(); -- 调用基类的 hello 方法
27. end
28.
29.
30. x= new (classB, 1) -- 新建一个 classB 的实例
31. x:print_x() -- 执行 print_x 方法,该方法实际是在基类 classA 中定义
32. x:hello() -- 调用 classB 的重载方法 hello
33.
输出结果:
classA constructor
classB constructor
1
hello classB
hello classA
实现类的多重继承
在类的单继承的基础上,只要对 object 稍加修改,就能让 Lua 支持多重继承,从而一举超越 Ruby 等大多数脚本语言,获得与长盛不衰的 C++语言相似的能力。
除单继承的语法外,我们定义 Lua 多重继承的几个新语法:
1. 如果 Class1 从 Class2 和 Class3 继承,使用语句: Class1 = Class2() + Class3()
2. 你也可以同时定义成员数据及函数,像这样:
Class1 = Class2() + Class3{
data1 = 1,
data2 = 2,
function constructor( self , p1, p2)
...
end ,
function func( self , p1, p2)
...
end
}
要实现上述语法,你只需要为 object 重新打造 __index 和 __add 两个元方法,完整的 object 定义如下:
01. object = {
02.
03. }
04.
05. setmetatable(object, {
06. __call = function ( super ,init)
07. local function createClass( super ,init)
08. local class = init or {}
09. class . super = { super }
10. class .constructor = false
11. class .instance= function (self, o, ...)
12. local obj={}
13. --copy from self( class prototype)
14. for k,v in pairs(self) do
15. obj[k] = v;
16. end
17. if (type(o) == "table" ) then
18. --copy from o
19. for k,v in pairs(o) do
20. obj[k] = v;
21. end
22. else
23. table.insert(arg, 1, o);
24. end
25.
26. do
27. local function call_constructor(c,...)
28. if c. super then
29. for _,v in ipairs(c. super ) do
30. call_constructor(v,...)
31. end
32. end
33. if c.constructor then
34. c.constructor(obj,...)
35. end
36. end
37. call_constructor(self,unpack(arg))
38. end
39. setmetatable(obj,{ __index = function (t,k)
40. for _,v in ipairs(t. super ) do
41. local nv = v[k]
42. if (nv ~= nil) then return nv end
43. end
44. end
45. })
46. return obj
47. end
48.
49.
50. setmetatable( class ,{
51. __call = createClass,
52. __index = function (t,k)
53. for _,v in ipairs(t. super ) do
54. local nv = v[k]
55. if (nv ~= nil) then return nv end
56. end
57. end,
58. __add = function (class1, class2)
59. --merge super classes
60. local sum={}
61. for k,v in pairs(class1) do
62. sum[k] = v
63. end
64. for k,v in pairs(class2) do
65. sum[k] = v
66. end
67. local super_sum={}
68. for _,v in ipairs(class1. super ) do
69. table.insert(super_sum,v)
70. end
71. for k,v in ipairs(class2. super ) do
72. table.insert(super_sum,v)
73. end
74. sum. super = super_sum
75. return sum
76. end
77. })
78.
79. return class
80. end
81.
82. return createClass( super ,init)
83. end
84. })
85.
86. function new ( class , init, ...)
87. return class :instance(init, ...)
88. end 同样,测试结果令人很满意(假设上述 object 的代码放置在 m_inheritance.lua 文件内):
01. dofile( "m_inheritance.lua" )
02.
03. classA = object{x=1,y=1}; -- classA 由 object 直接派生
04.
05. classB = classA{x=2,y=2}; -- classB 由classA 直接派生
06.
07. classC = object{z=0.4}; -- classC 由 object 直接派生
08.
09. usefulClass = classB() + classC{x=3, y=3}; -- usefulClass 多重继承于 classB 和 classC
10.
11. function classA:constructor() -- 定义 classA 的构造函数
12. print( "classA constructor" )
13. end
14.
15. function classA:print_x() -- 定义一个成员函数 classA:print_x
16. print( "x:" ..self.x)
17. end
18.
19. function classA:hello() -- 定义另一个成员函数 classA:hello
20. print( "classA_hello" )
21. print( "y:" ..self.y)
22. end
23.
24. function classB:constructor() -- 定义 classB 的构造函数
25. print( "classB constructor" )
26. end
27.
28. function classB:hello() -- classB:hello 重载 classA:hello
29. print( "classB_hello" )
30. print( "y:" ..self.y)
31. classA.hello(self);
32. end
33.
34.
35. function classC:constructor() -- 定义 classC 的构造函数
36. print( "classC constructor" )
37. end
38.
39. function classC:print_z() -- 定义 classC 的 print_z 函数
40. print( "z:" ..self.z)
41. end
42.
43.
44.
45. function usefulClass:constructor() -- 定义 usefulClass 的构造函数
46. print( "usefulClass_constructor" )
47. end
48.
49. x= new (usefulClass) -- 新建 usefulClass 的实例
50. x:print_x() -- 调用 print_x,实际是调用 classA:print_x
51. x:hello() -- 调用 hello,实际是调用 classB:hello
52. x:print_z() -- 调用 print_z, 实际是调用 classC:print_z 结果输出为:
> classA constructor
> classB constructor
> classC constructor
> usefulClass_constructor
> x:3
> classB_hello
> y:3
> classA_hello
> y:3
> z:0.4
看到这些,你是否也同意 Lua 算得上一门艺术呢?相信我,奇迹仅仅就是你,再加上 Lua !