菜狗教程 —— Lua 篇 02
上一篇概述了 Lua 的基本语法,距离实际上使用 Lua 还有很长的距离,第二篇我们就直接上一个大台阶,看看 Lua 的高级用法。
从 table 开始
表(table) 可以说是 Lua 最重要的数据类型,不仅功能丰富,还有很多巧妙的设计,绝对让人眼前一亮。
- table 支持
.运算符,t.key 等价于 t[key]。 - table 支持
:运算符,t:func() 等价于 t.func(t)。函数体内部可以用 self 访问 t.
元表(metatable)
meta 这个词在编程中非常常见,顾名思义就可以推测出元表的功能:修改表的行为。
举个例子,一般来说访问表中不存在的 key 无法取到值,Lua 的 table 确实会返回 nil。如果把这个值当成是 function 类型调用,程序就会报错。
t = {}
-- t.mFuncAdd = function(a, b)
-- return a + b
-- end
print(t.mFuncAdd(1, 2))
如果没有注释掉函数定义,输出结果应该是 3,现在的实际运行结果是一个异常:
如果 mFuncAdd 的赋值不在我们可以控制的范围里,又不想程序因此停止,我们可以通过元表修改表的行为,比如让这个表访问不到的 key 默认返回一个空函数替代 nil。
t = {}
-- Lua 中通过 `setmetatable(table, metatable)` 函数给一个表设定元表。
setmetatable(t, {
__index = function(t, key)
return function() end
end
})
print(t.mFuncAdd(1, 2))
成功了,我们通过修改元表的 __index 函数修改了表在找不到 key 时的行为。
此处有一个容易迷惑的点,元表也是一个普通的表,所以每个表都有 __index 字段。要修改 表 的行为,必须修改 它的元表 的 __index 才有效,修改它自己的 __index 是没效果的。
元表支持修改的字段和对应功能如下表:
| 元方法 | 说明 |
|---|---|
| __add | 重载加法操作符 + |
| __sub | 重载减法操作符 - |
| __mul | 重载乘法操作符 * |
| __div | 重载除法操作符 / |
| __mod | 重载取模操作符 % |
| __pow | 重载幂运算操作符 ^ |
| __unm | 重载一元减运算符 - |
| __concat | 重载连接运算符 .. |
| __len | 重载 # 操作符 |
| __eq | 重载等于运算符 == |
| __lt | 重载小于运算符 < |
| __le | 重载小于等于运算符 <= |
| __index | 重载索引访问操作符 [] |
| __newindex | 重载赋值操作符 []= |
| __call | 重载函数调用操作符 () |
| __tostring | 将表转换为字符串 |
表 + 函数 = 对象
Lua 虽然并不经常独立使用,但嵌入其他语言动态化的部分通常也会包含比较复杂的业务逻辑,毕竟特别简单的逻辑可以通过一些开关实现更简洁的动态化。对于复杂的业务逻辑,使用面向对象的编程思想往往更易于理解,Lua 的实际使用中经常使用 table + function 来模拟对象。
看一个简单的例子:
local obj = { name = "MyObject" }
function obj:toString()
print("name is "..self.name)
end
-- test
print(obj.name)
print(obj:toString())
我们的目的是模拟一个叫做 Obj 的类,添加了一个成员变量 name 和一个方法 toString。从使用的代码上看,无论是创建对象还是访问成员变量、调用方法,都和面向对象的代码有些相似。
在通过 Lua 动态化一部分业务逻辑的时候,Lua 的「对象」来自其他语言中预构造的表,更进一步可能是业务中一个对象的映射,通过这样的模拟对象,Lua 中的代码会更贴近业务代码,易于理解。
继承和多态
说到面向对象,显然上面的模拟还不够。众所周知,面向对象编程有封装、继承、多态、抽象的特性,模拟对象最多就是做了简单的封装。
第二节的元表并不是乱入的,Lua 通过元表的能力也可以模拟继承和多态。更详细点说,Lua 可以通过修改元表的 __index 实现继承,可以通过表自己定义同名函数覆写元表函数实现多态。
这次我们直接看代码吧,都是前面的知识结合:
-- Base Object
local Obj = { name = "Obj" }
Obj.__index = Obj
function Obj:new(name)
local self = setmetatable({}, Obj)
self.name = name
return self
end
function Obj:toString()
return self.name
end
local mObj = Obj:new("A")
print(mObj:toString())
-- Person extends Obj
local Person = Obj:new("Obj")
Person.__index = Person
function Person:new(name, age)
local self = setmetatable({}, self)
self.name = name
self.age = age
return self
end
local person = Person:new('菜狗', 2)
print(person:toString()) -- Obj:toString()
-- override toString
function Person:toString()
return '{name='..self.name..', age='..self.age..')'
end
print(person:toString()) -- Person:toString()
Person 继承自 Obj,可以直接调用父类的 toString,也可以重写 toString 函数实现自己的逻辑。