Lua 中如何实现继承
本文主要参考了菜鸟教程中的 Lua 面向对象,再加上自己学习过程的中思考,特此记录,如果文中有不对的地方,请不吝赐教。
这里就不在介绍面向对象的基本思想了,主要讲一讲 Lua 中如何实现继承,包括单继承和多继承
。
1、如何定义一个类
我们知道,对象由属性和方法
组成。Lua 中最基本的结构是table
,所以需要用 table 来描述对象的属性。
Lua 中的 function 可以用来表示方法。那么 Lua 中的类可以通过 table + function 模拟出来
。
那我们需要的继承,可以通过 metetable
模拟出来(不推荐用,只模拟最基本的对象大部分实现够用了)。
Lua 中的 table 不仅在某种意义上是一种对象
。既可以像对象一样,有自己的状态(成员变量)。同时也有与对象的值独立的本性
,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有。
这段话是看懵逼了,说下自己的理解吧,不对的话,欢迎评论区指出,十分感谢。
这段话主要在介绍 Lua 中的表(table)与对象的相似之处。表不仅仅是一种数据结构,它也有状态(成员变量),并且每个表都是独立的,即使两个表的值相同,它们也代表两个不同的对象。与对象类似,表也有生命周期,与其创建方式和创建位置无关。此外,表也有成员函数,可以通过函数来操作表的数据。
先提一嘴,下面的 new 方法是自定义的,并不是规定必须这样做,写成 abc 都是可以的。
-- 元类
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
上面的方式在创建对象时,需要手动传入 nil 参数,不太优雅,我们可以对其进行封装。
--创建一个类,表示四边形
local RectAngle = { length, width, area } --声明类名和类成员变量
function RectAngle: new (len,wid) --声明新建实例的New方法
local o = {
--设定各个项的值
length = len or 0,
width = wid or 0,
area =len*wid
}
setmetatable(o,{__index = self} )--将自身的表映射到新new出来的表中
return o
end
function RectAngle:getInfo()--获取表内信息的方法
return self.length,self.width,self.area
end
a = RectAngle:new(10,20)
print(a:getInfo()) -- 输出:10 20 200
b = RectAngle:new(10,10)
print(b:getInfo()) -- 输出:10 10 100
print(a:getInfo()) -- 输出:10 20 200
上面 就是 把 变量 o 的创建时候,放到了函数里面而已,并没特别的地方,但是可以省去我们调用时每次都需要传 nil 的麻烦。
2、前置知识
在学习如何使用 Lua 实现继承之前,我们先来学习几个 Lua 的基本知识,为后面的继承打下基础。
- table
- Metatable
2.1 table 的简单介绍
下面看看如何初始化表
-- 初始化 表
Stu1 = {age=10, sex=1}
-- 指定值
Stu1["name"] = "xiaoming"
-- 添加方法
function Stu1:getSex()
return self.sex
end
function Stu1:getName()
return Stu1.name
end
print(Stu1["name"]) -- xiaoming
print(Stu1["age"]) -- 10
print(Stu1.age) -- 10
print(Stu1:getSex()) -- 1
-- print(Stu1["getSex"]()) 报错
print(Stu1["getName"]()) -- xiaoming
对于上面不清楚的同学,可以看看我之前写的 lua中 . 和 : 的区别 这篇文章。
2.2 Metatable
下面的内容都是来自 Lua 元表(Metatable)
在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。
因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。
当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add 的字段,若找到,则调用对应的值。 __add 等即时字段,其对应的值(往往是一个函数或是 table)就是元方法。
有两个很重要的函数来处理元表:
- setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元表(metatable)。
以下实例演示了如何对指定的表设置元表:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
以上代码也可以直接写成一行:
mytable = setmetatable({},{})
以下为返回对象元表:
getmetatable(mytable) -- 这会返回 mymetatable
3.2.1 __index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index
键。如果__index
包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用 lua 命令进入交互模式查看:
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
如果__index
包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数
(这句话很重要,后面的多重继承就使用到了这个原理)。
__index
元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由__index
返回结果。
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
实例输出结果为:
value1 metatablevalue
实例解析:
mytable 表赋值为 {key1 = "value1"}。
mytable 设置了元表,元方法为 __index。
在mytable表中查找 key1,如果找到,返回该元素,找不到则继续。
在mytable表中查找 key2,如果找到,返回 metatablevalue,找不到则继续。
判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数。
元方法中查看是否传入 "key2" 键的参数(mytable.key2已设置),如果传入 "key2" 参数返回 "metatablevalue",否则返回 mytable 对应的键值。
我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
总结:
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 3.判断元表有没有
__index
方法,如果__index
方法为 nil,则返回 nil;如果__index
方法是一个表,则重复 1、2、3;如果__index
方法是一个函数,则返回该函数的返回值。
3、单继承
当我们学习了 Lua 中如何定义一个类后,就可以考虑,如何在 Lua 中实现单继承了。接下来,我们一起看看如何实现单继承。
-- 元类
local Base = {}
-- 基础类方法 new
function Base:new(name)
local o = {
name = name or "default"
}
--将自身的表映射到新new出来的表中
setmetatable(o, self)
self.__index = self
return o
end
function Base:run()
print(self.name .. " is running....")
end
-- 创建对象
b = Base:new("Base")
b:run()
print(string.rep("*", 30))
-- 创建派生类对象
-- 重点理解这里为何需要这样初始化对象,而不能像这样 T3 = {} 初始化变量 T3
-- 原因就在于 T3:new() 中 self 变量就是 T3 这个变量,当使用 setmetseatable、__index 时,才会给
-- T3:new() 中的变量o 添加上 Base 定义的属性和方法。
T3 = Base:new()
-- 派生类方法 new
function T3:new(name)
-- 实现继承,这里实现继承不仅仅是靠下面这一句实现的
-- 而是 加上 setmetatable、__index 才模拟出 继承的现象
local o = Base:new(name)
setmetseatable(o, self)
self.__index = self
return o
end
local t3 = T3:new("T3")
t3:run()
print(string.rep("*", 30))
-- 创建派生类对象
BYD = Base:new()
-- 派生类方法 new
function BYD:new(name)
-- 实现继承,这里实现继承不仅仅是靠下面这一句实现的
-- 而是 加上 setmetatable、__index 才模拟出 继承的现象
local o = Base:new(name)
setmetatable(o, self)
self.__index = self
return o
end
-- 派生类创建方法
function BYD:power()
print(self.name .. " 正在充电.....")
end
local byd = BYD:new("BYD")
byd:run()
byd:power()
print(string.rep("*", 30))
运行结果:
Base is running....
******************************
T3 is running....
******************************
BYD is running....
BYD 正在充电.....
******************************
从上面可以看到,T3、BYD 这两个类 继承 了 Base 类的 run 方法。
4、多继承
4.1 线性多重继承
A1 = {}
function A1:new(name)
local o = {
name = name or "A1"
}
setmetatable(o, {__index = self})
return o
end
function A1:printA1()
print(self.name .. "is coming printA1")
end
-- 重点理解这里为何需要这样定义,而不能 A2 = {} 这样初始化变量 A2
A2 = A1:new()
function A2:new(name)
-- 这样就可以继承A1定义的方法
local o = A1:new(name)
setmetatable(o, {__index = self})
return o
end
function A2:printA2()
print(self.name .. "is coming printA2")
end
A3 = A2:new()
function A3:new(name)
local o = A2:new(name)
setmetatable(o, {__index = self})
return o
end
function A3:printA3()
print(self.name .. "is coming printA3")
end
-- 创建对象
a = A3:new("I'm a ")
a:printA1()
a:printA2()
a:printA3()
运行结果:
I'm a is coming printA1
I'm a is coming printA2
I'm a is coming printA3
上面这种方法,是比较容易想到的实现多重继承的方式,但是这种方式有一定的局限性,继承不够灵活。下面再来看看另外一种继承方式,相当而言就会灵活很多。
4.2 更加灵活的多重继承
-- 在 table 变量 cList 中查找 k
local function search(k, cList)
for i=1,#cList do
local v = cList[i][k] -- 尝试第i个基类
if v then
return v
end
end
end
function createClass(...)
local c = {}
print("c 的地址 -->", c)
local parents = {...}
-- 类在其父类列表中的搜索方法
setmetatable(c, {__index = function(t, k)
-- 这里传入的 t 就是 刚刚定义的 c
-- 如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
-- __index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。
print("t 的地址 -->", t)
print("k -->", k)
return search(k, parents)
end})
-- 将'c'作为其实例的元表
c.__index = c
-- 为这个新类定义一个新的构造函数
function c:new(o)
o = o or {}
setmetatable(o, c)
return o
end
return c -- 返回新类
end
-- 类Named
Named = {}
function Named:getname()
return self.name
end
function Named:setname(n)
self.name = n
end
-- 类Account
Account = {balance = 0}
function Account:withdraw(w)
self.balance = self.balance - w
end
print("Account -->", Account['withdraw'])
-- 创建一个新类NamedAccount,同时从Account和Named派生
NamedAccount = createClass(Account, Named)
account = NamedAccount:new()
account:setname("Ives")
print(account:getname()) -- 输出 Ives
account:withdraw(10)
print(account.balance)
运行结果:
Account['withdraw'] --> function: 039BD710
c 的地址 --> table: 039BB7E8
t 的地址 --> table: 039BB7E8
k --> setname
t 的地址 --> table: 039BB7E8
k --> getname
Ives
t 的地址 --> table: 039BB7E8
k --> withdraw
t 的地址 --> table: 039BB7E8
k --> balance
-10
参考资料:
Lua 中如何实现继承的更多相关文章
- lua中基类和“继承机制”
基类:基类定义了所有对于派生类来说普通的属性和方法,派生类从基类继承所需的属性和方法,且在派生类中增加新的属性和方法. 继承:继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类. lu ...
- cocos2dx lua中继承与覆盖C++方法
cocos2dx的extern.lua中的class方法为lua扩展了面向对象的功能,这使我们在开发中可以方便的继承原生类 但是用function返回对象的方法来继承C++类是没有super字段的,这 ...
- 【游戏开发】在Lua中实现面向对象特性——模拟类、继承、多态
一.简介 Lua是一门非常强大.非常灵活的脚本语言,自它从发明以来,无数的游戏使用了Lua作为开发语言.但是作为一款脚本语言,Lua也有着自己的不足,那就是它本身并没有提供面向对象的特性,而游戏开发是 ...
- 【转载】【游戏开发】在Lua中实现面向对象特性——模拟类、继承、多态
[游戏开发]在Lua中实现面向对象特性——模拟类.继承.多态 阅读目录 一.简介 二.前提知识 三.Lua中实现类.继承.多态 四.总结 回到顶部 一.简介 Lua是一门非常强大.非常灵活的脚本语 ...
- Lua面向对象----类、继承、多继承、单例的实现
(本文转载)学习之用,侵权立删! 原文地址 http://blog.csdn.net/y_23k_bug/article/details/19965877?utm_source=tuicool&a ...
- lua 中的面向对象
lua 是一种脚步语言,语言本身并不具备面向对象的特性. 但是我们依然可以利用语言的特性,模拟出面向对象的特性. 面向对象的特性通常会具备:封装,继承,多态的特性,如何在lua中实现这些特性,最主要的 ...
- Lua类和类继承实现
Lua本身是不能像C++那样直接实现继承,但我们可以用万能的table表来实现. 以下我总结了三种方式的类以及继承的实现 第一.官方的做法,使用元表实现 原理参照<Programming in ...
- Cocos2d-x 脚本语言Lua中的面向对象
Cocos2d-x 脚本语言Lua中的面向对象 面向对象不是针对某一门语言,而是一种思想.在面向过程的语言也能够使用面向对象的思想来进行编程. 在Lua中,并没有面向对象的概念存在,没有类的定义和子类 ...
- lua中的面向对象编程
简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 上述代码会输出tb1 ~= tb2.说明两个具有相同值得对象是两个不同的对象,同时在Lua中table是引用类型的 ...
- Lua中metatable和__index的联系
Lua中metatable和__index的联系 可以参考 http://blog.csdn.net/xenyinzen/article/details/3536708 来源 http://blog. ...
随机推荐
- [转帖]Nginx 性能测试
https://plantegg.github.io/2022/10/10/Nginx%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/ 压测工具选择 wrk ,apache ...
- Redis-dump Docker搭建的快速指南
背景 最近学习redis想能够将dump文件进行导入处理. 看到比较好的办法都是使用ruby ,但是公司的网络太感人了. 想着比较简单的办法是通过docker方式来搭建. 这里简单记录一下搭建过程. ...
- Windows 审计日志 安全部分不刷新的解决办法
现在存在一个问题如图示: 有接近15个小时的日志没有进行记录和展示. 要追查问题比较麻烦. 后来发现必须要手动刷新一下 审计记录才可以实现. 感觉比较奇怪 位置为 计算机配置->windows ...
- 01 vue子组件调用父组件中的方法
vue子组件,调用父组件中有三种方法哈!下面我们一起来讲解. 第一种使用 直接在子组件中通过this.$parent.父组件中的方法.来调用父组件的方法 第一种的缺点是不能够传递参数哈.它只能够调用方 ...
- 【发现一个问题】VictoriaMetrics中,vm-select与vm-storage之间的协议存在版本兼容性问题
使用中发现,vm-select 1.76版本,查询vm-storage的1.70版本,报以下错误: cannot execute rpcName="search_v5" on vm ...
- 【Jmeter】基础介绍-详细
最近做压测时使用到Jmeter,为什么用它,之前也做过部分压测,不是很系统,使用的是Apache Bench,虽然效率高,但是功能比较简单,不太适合本次压测场景,另外Jmeter能更好的利用压测机的多 ...
- DAPR-分布式系统运行时简介
Dapr全称Distributed Application Runtime,翻译过来就是分布式应用程序运行时,在v1.0发布后得到了极大的发展.本章将向你介绍Dapr架构的核心概念,为您使用Dapr进 ...
- (C语言)我的第一个项目:命令行窗口下的学生成绩管理系统,及数据生成程序
学生成绩管理系统 页面效果如图: 代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> ...
- 【scikit-learn基础】--『分类模型评估』之评估报告
分类模型评估时,scikit-learn提供了混淆矩阵和分类报告是两个非常实用且常用的工具.它们为我们提供了详细的信息,帮助我们了解模型的优缺点,从而进一步优化模型. 这两个工具之所以单独出来介绍,是 ...
- delphi 官方例子 simples 路径
公用 文件件 可能是隐藏的 若是的话 则显示 隐藏