首页 > 其他分享 >Lua 中如何实现继承

Lua 中如何实现继承

时间:2023-06-17 18:34:20浏览次数:39  
标签:__ index end 如何 继承 self Lua new --

本文主要参考了菜鸟教程中的 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 面向对象

标签:__,index,end,如何,继承,self,Lua,new,--
From: https://www.cnblogs.com/huageyiyangdewo/p/17488042.html

相关文章

  • [ Shell ] 在 Bash 中如何使用“字典”
    https://www.cnblogs.com/yeungchie/定义declare-Adict赋值批量赋值dict=([a]=1[b]=2[c]=3)追加赋值dict[lib]=topdict[cell]=XX1234dict[view]=layout取值取值方式与数组一样。echo"${dict[a]}"#1echo"${dict[cell]}"#XX1234打印所有key和value......
  • 如何开头脑风暴会?
    IDEO是一家美国的设计和咨询公司,以创新的设计思维方法和人本主义的设计理念而闻名。他们提出的头脑风暴七原则可以帮助团队有效地进行脑风暴会议,激发创新的思想和想法。一、暂缓评判(DeferJudgment)在早期的脑风暴阶段,抑制批评和评判的冲动,让所有的想法都有被听到和记录下来的机......
  • 若依框架 前后端分离如何通过菜单管理添加菜单?
    1、创建主目录菜单,如图: 路由地址填写views下的文件名,目录结构参考如下:2、创建子菜单,点击新增 3、填写子菜单信息,参考如下: 路由地址填写:views下的二级文件夹名字组件路径填写为:views下到index.vue的路径(不要写文件后缀),例如:publish/mipsMaterialFiles/index权限字符......
  • 面向对象-继承
    面向对象的继承是一种机制,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承可以使代码重用、提高代码的可扩展性和灵活性。通过继承,子类可以继承父类的非私有属性和方法,并且可以在子类中添加自己特定的属性和方法。子类可以继承父类的行为,也可以重新定......
  • 如何通过店铺 ID 获取淘宝店铺商品数据上传至京东店铺,整店商品数据搬家,淘宝店铺所有商
    在电商行业还是销量为王,因此在很多相关行业,比如商品搬家行业都需要用到相关的商品详情信息,但是官方一般又没有开放这些接口,怎么办?  解决方案现在很多是通过爬虫获取数据,但是根本扛不住大量调用,不稳定,真能稳定获取了也不是一般人有能力去做的,加上淘宝变化频繁,防爬越来越严,技术难......
  • 设计模式:适配器模式(论如何把鼠头适配成鸭脖)
    适配器模式(AdapterPattern)有时候也称包装样式或者包装,是一种结构型设计模式,它可以将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以让原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式有三种类型:类适配器模式、对象适配器模式和接口适配器模式......
  • 如何通过专业化物业管理系统开发,提升物业管理效率?
    物业管理需要高效的工具来支持,这就需要专业化的物业管理系统开发。随着现代科技的发展,专业化物业管理系统作为一种新型的信息管理系统,被广泛应用于物业管理领域。而专业化物业管理系统开发可以帮助物业管理公司提升管理效率,提高服务质量,同时也能够有效降低物业管理成本。接下来广州......
  • java中 如何在文本中筛选出汉字
    在Java中,使用正则表达式来筛选出文本中的汉字。下面是一种方法:importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassMain{publicstaticvoidmain(String[]args){Stringtext="Hello你好!Thisisatest文本。";//使......
  • java中 如何在文本中筛选出汉字
    在Java中,使用正则表达式来筛选出文本中的汉字。下面是一种方法:importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassMain{publicstaticvoidmain(String[]args){Stringtext="Hello你好!Thisisatest文本。";//使......
  • 服务器CPU和普通家用CPU的区别,服务器如何选择CPU配置?
    大家都知道服务器是指那些具有较高计算能力,能够提供响应成千上万用户服务请求的一种高性能计算机服务器,虽然在硬件结构上和通用的个人计算机(PC)非常相似,但两者在性能和功能上相差甚远,其中服务器的CPU和普通家用的CPU也是一样的,今天咱们来分析一下CPU间的不同之处1.指令集不同家用或......