目录
一些废话
测试代码
代码说明
基类(父类) A
派生类(子类) B
子类对象操作私有变量
继续往下继承
Lua
语言是一个小而美的语言,使用者不多。
估计阅读这篇文章的人也不会多,姑且当做一篇笔记吧。
这篇文章主要描述:在Lua
语言中,如何通过table
结构来实现面向对象编程。
主要是看到某鸟教程上错误百出,估计示例代码自己都没有测试过;
关于Lua
语言中的table
以及metatable
的基本知识,这里就不赘述了,官方手册中描述的很清楚。
1 #!/usr/bin/lua
2
3 ------------------------------ class A
4 A = {
5 a = 1,
6 funcA = function()
7 print("this is funcA")
8 end
9 }
10
11 function A:new(t)
12 local t = t or {}
13 self.__index = self
14 setmetatable(t, self)
15 return t
16 end
17
18 function A:myadd(num)
19 self.a = self.a + num
20 end
21
22 objA = A:new()
23 print("objA.a = " .. objA.a)
24 print(objA.funcA())
25 print(string.rep("-", 20))
26
27 ------------------------------ class B
28 B = A:new({
29 b = 2,
30 funcB = function()
31 print("this is funcB")
32 end
33 })
34
35 objB = B:new()
36 print("objB.a = " .. objB.a)
37 print("objB.b = " .. objB.b)
38 print(objB.funcA())
39 print(objB.funcB())
40
41 objB:myadd(10)
42 print("objA.a = " .. objA.a)
43 print("objB.a = " .. objB.a)
执行结果如下:
$ ./oop.lua
objA.a = 1
this is funcA
--------------------
objB.a = 1
objB.b = 2
this is funcA
this is funcB
objA.a = 1
objB.a = 11
首先来分析下4-25
行的代码。
4-9
行:定义父类A
的成员变量和函数(按照C++
中的习惯,可以叫做方法),可以看出Lua
语言中的函数是“一等公民”,是可以赋值给一个变量的。
11-16
行:相当于是构造函数,用来创建一个父类A
的对象。
18-20
行:给父类A
增加一个函数,待会在分析子类B
的时候再说。
22
行:调用A:new()
函数,创建一个类A
的对象,赋值给变量objA
。
在A:new()
函数中,关键是第13
行代码:此时self
等于A
,就相当于是A.__index = A
,这是合法的。
因为函数的调用方式是A:new()
,Lua
的语法糖会把A
作为第一个参数传递给new()
函数的第一个隐藏参数self
。
然后执行14
行的setmetatable(t, self)
,相当于把表t
的元表设置为A
。
以上两行搞明白之后,23-24
行的打印语句就简单了:
23
行:因为表objA
中没有成员a
,但是objA
被设置了元表A
,而且该元表A
带有__index
属性,该属性的值是表A
自己,于是就到A
中查找是否有成员a
,于是就打印出:
objA.a = 1
__index 属性的值,可以是一个表,可以是一个函数;
只不过这里特殊一点:__index 设置为 A 自己;
24
行:查找函数的过程是一样的,找到元表A
的__index
属性的值,也就是表A
自己中的funcA
函数,然后调用,打印出:
this is funcA
28-33
行:定义了子类B
,其实它也是一个对象。
在创建函数A:new(t)
中,参数t
的值是:
local t = {
b = 2,
funcB = function()
print("this is funcB")
end
}
此时,self
仍然是父类A
,B
的创建过程与objA
的创建过程是一样的,只不过给参数t
设置了子类B
自己的成员变量和函数。
所以,B
的元表被设置为A
(14
行代码的功劳),当然了A
的__index
仍然被设置为A
自己。
关键是35
行:objB = B:new()
,得仔细唠唠。
子类B
并没有自己的new
函数,但是类B
(也是一个 table
) 的元表被设置为A
,并且A.__index = A
,所以最终就找到了A
中的new
函数,也就是11-16
行代码。
进入这个函数中时,第一个隐藏参数self
被设置为 B 了,因为函数调用形式是:B:new()
。
所以:
13 行 self.__index = self 相当于设置 B.__index = B
14 行 etmetatable(t, self) 相当于把表 t 的元表设置为 B
new()
函数返回之后,就把t
赋值给objB
。
下面再看一下36-39
行的打印语句:
36 print("objB.a = " .. objB.a)
37 print("objB.b = " .. objB.b)
38 print(objB.funcA())
39 print(objB.funcB())
36
行:objB
中并没有成员a
,但是objB
的元表是B
,而且B.__index = B
,所以就到B
中去查找a
。
虽然B
中也没有a
,但是B
的元表是A
,而且A.__index = A
,所以就在A
中找到了成员a
,打印出:
objB.a = 1
37
行:objB
中并没有成员b
,但是objB
的元表是B
,而且B.__index = B
,所以在B
中找到了成员b
,因此打印出:
objB.b = 2
37
和38
行的查找过程是类似的,只不过换成了函数而已。
41
行:objB:myadd(10)
。
查找myadd
函数的过程与查找obj.a
的过程是一样的,这里再唠叨一遍:
- objB 中并没有函数 myadd,但是 objB 的元表是 B,而且 B.__index = B,所以就到 B 中去查找 myadd;
- 虽然 B 中也没有 myadd,但是 B 的元表是 A,而且 A.__index = A,所以就在 A 中找到了函数 myadd;
于是就调用了函数:
18 function A:myadd(num)
19 self.a = self.a + num
20 end
而且self
等于objB
,因此函数体中就等于是:
objB.a = objB.a + 10
加法表达式中的objB.a
的读取过程,上面已经描述过了,最终定位到的是父类A
中的a
,即:1
。
1 + 10 = 11
,然后把11
赋值给objB.a
。
在赋值操作中,被赋值的objB.a
就不再是父类A
中的那个a
了!
因为objB
本质是一个table
,给objB
设置键值对的时候:
- 如果键已经存在了,那么就直接设置该键的值;
- 如果键不存在,那么 lua 会看它的元表中是否有 __newindex 字段(可以是一个table,也可以是一个函数);
2-1. 如果有 __newindex 字段,那么就是调用 __newindex (如果是一个函数),或者在 __newindex 中添加键值对(如果是一个table);
2-2. 如果没有 __newindex 字段,那么就直接在 objB 中存储该键值对;
根据上面这个规则,就会设置objB.a = 11
。
明白以上这些之后,42
和43
行的打印语句就不复杂了。
42
行:objA
最终找到的a
是父类A
中的成员a
,打印出:objA.a = 1
。
43
行:objB
中自己已经有了成员a
,所以打印出:objB.a = 11
。
有了上面的基础,再从子类B
中派生出类C
,C
派生出类D
... 都不是什么问题了,如下所示:
C = B:new()
objC = C:new()
print("objC.a = " .. objC.a)
print("objC.b = " .. objC.b)
print(objC.funcA())
print(objC.funcB())
感兴趣的读者可以自己测试一下。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8