因为Redis要用到Lua脚本,就去浏览了一遍Lua语法,这里记录一下对协同程序和面向对象的理解。
协同程序(coroutine)
看起来很像Go的协程(goroutine),但是其实不是一回事。
Go的协程与Lua协同程序的主要区别在于,一个具有多个Go协程的程序可以同时运行多个Goroutine的,而协同程序却需要彼此协作的运行。
注意以下两点:
- 任一指定时刻,只有一个协同程序正在运行
- 正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
这意味着,其实Lua还是单线程(或者说同步的)执行程序。
这里以生产者消费者模式举例。Ps.我加了很多print用于观察
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
local newProductor local function productor() print("进入 productor") local i = 0 while true do i = i + 1 if i <= 3 then Send(i) else break end end print("离开 productor") end local function consumer() print("进入 consumer") while true do local i = Receive() if i ~= nil then print(i) else break end end print("离开 consumer") end function Send(x) print("进入 Send") coroutine.yield(x) print("离开 Send") end function Receive() print("进入 Receive") local status, value = coroutine.resume(newProductor) print("离开 Receive") return value end newProductor = coroutine.create(productor) consumer() |
productor() 就是生产者函数, consumer() 就是消费者函数。
生产者函数负责Send数据, 消费者函数负责Receive数据。
Send本质其实是挂起协同程序(coroutine.yield),将值传递出去。
Receive本质是唤醒协同程序(coroutine.resume),接受数据值。
所以实际上程序的整体执行顺序如下,
- 创建生产者协同程序
- 调用消费者函数consumer
- consumer调用Receive函数
- Receive函数唤醒协同程序productor
- 协同程序productor调用Send函数,挂起函数yield发送数据
- Received函数接收到值后,返回给消费者函数consumer
- 消费者函数打印输出值,再次调用Received函数
- 再次唤醒协同程序…注意!!!上次我们的协同程序运行到了print(“离开 Send”)这一行代码的前一行。
最终打印输出内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[xiong@AMDServer lua]$ lua pc.lua 生产者协同程序创建完毕 进入 consumer 进入 Receive 进入 productor 进入 Send 离开 Receive 1 进入 Receive 离开 Send 进入 Send 离开 Receive 2 进入 Receive 离开 Send 进入 Send 离开 Receive 3 进入 Receive 离开 Send 离开 productor 离开 Receive 离开 consumer |
能够对的上…嗯,就是这么个意思。
面向对象
面向对象三大特性:
- 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
对象一般由属性和方法组成,在Lua中可以用table来表示属性,用function表示方法。
继承的话,跟元表matetable看起来很像。
咱们直接上实例吧,这段代码是来自于菜鸟教程,但是做了一些修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
Shape = {area = 0} -- 1. 点号调用和冒号调用的区别... -- function Shape:new(o, side) -- o = o or {} -- setmetatable(o, self) -- self.__index = self -- side = side or 0 -- o.area = side * side -- return o -- end -- 使用点号需要显式传入self参数 function Shape.new(self, o, side) o = o or {} setmetatable(o, self) self.__index = self side = side or 0 o.area = side * side return o end -- 使用冒号不需要传self参数 function Shape:printArea() print("面积为:", self.area) end -- 点号调用需要传入self local myshape1 = Shape.new(Shape, nil, 10) myshape1.printArea(myshape1) -- 冒号调用不需要传入self local myshape2 = Shape:new(nil, 5) myshape2:printArea() -- 2. Square派生自Shape Square = Shape:new() function Square:new(o, side) o = o or Shape:new(o, side) setmetatable(o, self) self.__index = self return o end -- 派生类继承父类方法 -- function Square:printArea() -- print("正方形面积为:", self.area) -- end local mysquare = Square:new(nil, 3) mysquare:printArea() -- 3. Rectangle派生自Shape Rectangle = Shape:new() function Rectangle:new(o, length, breadth) o = o or Shape:new(o) setmetatable(o, self) self.__index = self o.area = length * breadth return o end -- 派生类重载父类方法 function Rectangle:printArea() print("矩形面积为:", self.area) end -- 测试对象B的创建不影响对象A print("") print("测试对象B的创建不影响对象A") local myRectangleA = Rectangle:new(nil, 10, 20) myRectangleA:printArea() local myRectangleB = Rectangle:new(nil, 10, 2) myRectangleB:printArea() myRectangleA:printArea() -- 测试对象A的修改不影响对象B print("") print("测试对象A的area修改为400不影响对象B") myRectangleA.area = 400 myRectangleA:printArea() myRectangleB:printArea() -- 测试myRectangleA新增元素abc对元表的影响 print("") print("测试新增元素abc对元表和其他对象无影响") myRectangleA.abc = 1 print("myRectangleA.abc", myRectangleA.abc) print("myRectangleB.abc", myRectangleB.abc) print("Rectangle.abc", "", Rectangle.abc) -- 最后我们删除abc属性 print("") print("删除abc的属性后") myRectangleA.abc = nil print("myRectangleA.abc", myRectangleA.abc) print("myRectangleB.abc", myRectangleB.abc) print("Rectangle.abc", "", Rectangle.abc) -- 然后删除RectangleA的area属性 print("") print("删除RectangleA的area属性") myRectangleA.area = nil print("myRectangleA.area", myRectangleA.area) print("myRectangleB.area", myRectangleB.area) print("Rectangle.area", "", Rectangle.area) -- 修改元表数据, 查看对对象A和B的影响 print("") print("修改元表Rectangle的area属性值为1") Rectangle.area = 1 print("myRectangleA.area", myRectangleA.area) print("myRectangleB.area", myRectangleB.area) print("Rectangle.area", "", Rectangle.area) -- 删除元表的area数据, 查看对对象A和B的影响 print("") print("删除元表Rectangle的area属性") Rectangle.area = nil print("myRectangleA.area", myRectangleA.area) print("myRectangleB.area", myRectangleB.area) print("Rectangle.area", "", Rectangle.area) -- 这里可以看到, 虽然元表的数据被删除了,但是B本身的存储的数据没有影响。 -- 问题是, 为什么A 和 Rectangle的数据还是0呢...那是因为Rectangle还有Shape元表拉~~~ |
这里做一些讲解。
lua的函数是可以 “.”(点号) 或者 “:” (冒号)调用的,它们的区别就是冒号调用省略了第一个参数self。
比如我们可以仅修改Shape:new 函数为 Shape.new(Shape, …),这种情况下还是可以正常调用的。
第二是对new函数做讲解。
1 2 3 4 5 6 7 8 9 |
-- 派生 Rectangle = Shape:new() function Rectangle:new(o, length, breadth) o = o or Shape:new(o) setmetatable(o, self) self.__index = self o.area = length * breadth return o end |
Rectangle是Shape的一个子类,我们来看看它的new方法Rectangle:new。
首先o是传入的参数,一般是nil,那么会取为Rectangle的基类Shape。
然后将self(也就是我们的Rectangle对象)设置为 o 的元表。
这种情况下,我们回忆下元表的规则:
- setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元表(metatable)。
所以我们将Rectangle设置为0的元表,将o的元表Rectangle的__index元方法设置为Rectangle自己。
Lua的元表查询规则:表中没有去元表的__index查找,元表中没有就去元表的元表…这样就实现了继承。
然后我们对o的area属性赋值,在菜鸟教程中,这个值是给了元表,但是这样是不合理的。具体的你创建两个对象,传递不同的参数进去,试试就明白了。
嗯,以上是我对Lua的协同程序,面向对象的理解…没了。