Lua 面向对象(详解)

news2024/12/23 18:23:13

Lua 面向对象(详解)

参考文章:

https://blog.csdn.net/linxinfa/article/details/103254828

https://zhuanlan.zhihu.com/p/115159195

https://blog.codingnow.com/cloud/LuaOO

https://blog.codingnow.com/2006/06/oo_lua.html

  Lua的面向对象主要是参考云风大神的进行编写,在云风大神源码的基础上考虑了一点细节,构造代码如下:

local _class = {}

function class(super)
    local tbClassType = {}
    tbClassType.Ctor  = false
    tbClassType.super = super
    tbClassType.New   = function(...)
        local tbObj   = {}
        do
            local funcCreate
            funcCreate = function(tbClass,...)
                if tbClass.super then
                    funcCreate(tbClass.super,...)
                end
                
                if tbClass.Ctor then
                    tbClass.Ctor(tbObj,...)
                end
            end
            funcCreate(tbClassType,...)
        end
        -- 防止调用Ctor初始化时,在Ctor内部设置了元表的情况发生
        if getmetatable(tbObj) then
            getmetatable(tbObj).__index = _class[tbClassType] 
        else
            setmetatable(tbObj, { __index = _class[tbClassType] })
        end
        return tbObj
    end

    local vtbl          = {} 
    _class[tbClassType] = vtbl

    setmetatable(tbClassType, { __newindex = 
        function(tb,k,v)
            vtbl[k] = v
        end
    })

    if super then
        setmetatable(vtbl, { __index = 
            function(tb,k)
                local varRet = _class[super][k]
                vtbl[k]      = varRet
                return varRet
            end
        })
    end
    return tbClassType
end


baseType = class()

function baseType:Ctor(x)
    print("baseType Ctor")
    self.x = x
    -- do something
    -- 包括setmetatable(self,{xxx})
    -- 由于在New方法内部已经考虑如果在Ctor函数中设置元表,外部则直接对__index赋值。毕竟一个表只能关联一张元表
end

function baseType:PrintX()
    print(self.x)
end


function baseType:Hello()
    print('hello baseType')
end


test = class(baseType)

function test:Ctor()	-- 定义 test 的构造函数
	print("test Ctor")
end
 
function test:Hello()	-- 重载 base_type:hello 为 test:hello
	print("hello test")
end


a = test.New(1)
a:PrintX()
a:Hello()

a1 = test.New(2)	
a1:PrintX()
a1:Hello()
a:PrintX()	

输出结果:

效果图

  下面对上述代码进行剖析以及注释。上述代码不仅仅实现类的封装以及继承,对元表中的元方法进行重载也已经考虑。

-- 记录所有的类模板以及其对应的方法,同时可以利用在继承关系下快速索引父级属性以及方法。
local _class = {}

-- 构建一个类,返回一个类对象,参数为基类(可不填)
function class(super)
    -- 可以理解为构建一个类模板
    local tbClassType = {}
    
    -- 构造函数,可以理解为C++的纯虚函数(接口)
    tbClassType.Ctor  = false
    
    -- 子类记录父类
    tbClassType.super = super

    -- New成员方法(匿名方法,闭包),只要用于通过类模板实例化一个类对象(实例)
    tbClassType.New   = function(...)
        -- 实例类
        local tbObj   = {}
        do
            -- 用于递归调用,如果有基类,且基类有构造函数,那么先调用基类的构造函数;否则直接调用当前类的构造函数
            local funcCreate
            funcCreate = function(tbClass,...)
                if tbClass.super then
                    -- 如果有基类,优先调用基类的ctor函数(递归,直至没有上一级为止)
                    funcCreate(tbClass.super,...)
                end
                
                if tbClass.Ctor then
                    -- 调用构造函数
                    tbClass.Ctor(tbObj,...)
                end
            end
            funcCreate(tbClassType,...)
        end
        -- 防止调用Ctor初始化时,在Ctor内部设置了元表的情况发生
        -- _class[tbClassType]就是类模板绑定的成员,类实例同样需要持有
        if getmetatable(tbObj) then
            getmetatable(tbObj).__index = _class[tbClassType] 
        else
            setmetatable(tbObj, { __index = _class[tbClassType] })
        end
        return tbObj
    end

    -- 这个table保存所有类模板的成员,实现过程中主要保存的是类的成员方法
    local vtbl          = {} 
    _class[tbClassType] = vtbl

    -- tbClassType 新增成员的时候,存到vtbl中
    setmetatable(tbClassType, { __newindex = 
        function(tb,k,v)
            vtbl[k] = v
        end
    })

    if super then
        -- 如果当前类继承了父类,那么在访问成员时,如果当前类中不具有该成员,则优先从_class中保存的父级中读取;
        -- 然后再将父类的成员赋值给当前类模板的vtbl容器。
        -- 简单地说,就似乎如果一个类实例有需要访问的成员则直接调用,否则从该类的上一级查找。
        setmetatable(vtbl, { __index = 
            function(tb,k)
                local varRet = _class[super][k]
                vtbl[k]      = varRet
                return varRet
            end
        })
    end
    return tbClassType
end


baseType = class()

function baseType:Ctor(x)
    print("baseType Ctor")
    self.x = x
end


function baseType:PrintX()
    print(self.x)
end


function baseType:Hello()
    print('hello baseType')
end


test = class(baseType)

function test:Ctor()	-- 定义 test 的构造函数
	print("test Ctor")
end
 
function test:Hello()	-- 重载 base_type:hello 为 test:hello
	print("hello test")
end


a = test.New(1)
a:PrintX()
a:Hello()

a1 = test.New(2)	
a1:PrintX()
a1:Hello()
a:PrintX()	

说明点

  上面有些细节的地方需要阅读一下知识点,有助于理解上文代码。

__index和__newindex

  当我们一个表绑定一个元表时,如果为访问表的属性,那么触发的是__index,而如果是修改表的属性,那么触发的是__newindex。也就是说,我们在修改表的属性时并不会遵循__index的查找规则!

tbTest = {}
setmetatable(tbTest,{ __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      print(key)
    end
  end})

tbTest.name = "ufgnix0802" -- 这里并不会触发__index这个元方法
print(tbTest.name)         -- 触发__index元方法!

getmetatable(tbTest).__newindex =  function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end

tbTest.key = "ufgnix0802" -- 触发__newindex元方法!

‘self’和’:'的关系

  在lua中,表拥有一个标识:self。self类似于this指针,大多数面向对象语言都隐藏了这个机制,在编码时不需要显示的声明这个参数,就可以在方法内使用this(例如C++和C#)。在lua中,提供了冒号操作符来隐藏这个参数,例如:

local t = {a = 1, b = 2}
function t:Add()
    return (self.a + self.b)
end

print(t:Add())

  冒号的作用有两个:1. 对于方法定义来说,会增加一个额外的隐藏形参(self);2. 对于方法调用来说,会增加一个额外的实参(表自身)。

  冒号只是一种语法机制,提供的是便利性,并没有引入任何新的东西。使用冒号完成的事情,都可以使用点语法来完成。看下面的例子:

local t = {a = 1, b = 2}
function t:Add()
    return (self.a + self.b)
end
function t.Sub(self)
    return (self.a - self.b)
end

print(t.Add(t))
print(t:Sub())

  从上述我们可以很清楚的知道,‘self’和‘:’配对出现,而我们也可不使用这种方式,但是必须传递类本身。那么关键问题来了,如果方法要调用的方法使用了’:‘符号,但我们又不想使用’:'调用呢?那么是不是我们得传递一个类对象本身?这也是上文代码New函数中funcCreate的调用方式为什么多了一个参数,也就是第一个参数tbClass的原因了。

  同时,我们需要留意一个细节,那就是上文代码第30行,我们传入的是tbObj,即我们即将构建的类对象实例的指针,而不是第90行的test对象本身。这也是为什么在第92行打印self 的地址跟test不相等的原因。感兴趣的可以尝试一下,self的地址跟101行的a以及105行的a1相同。

实际使用实例

文章链接索引:

https://blog.csdn.net/qq135595696/article/details/128827673

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/190292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Homekit智能家居DIY-智能插座

WiFi智能插座对于新手接触智能家居产品更加友好,不需要额外购买网关设备 很多智能小配件也给我们得生活带来极大的便捷,智能插座就是其中之一,比如外出忘记关空调,可以拿起手机远程关闭。 简单说就是:插座可以连接wi…

不再一个个试错,这众多的flex属性

流式布局 本篇我们将从流式布局的四大方面入手,旨在认识、了解、以至于掌握其特性及功能。 为什么要用? 在我们抛弃标准流、定位流以及浮动流之后,取而代之的是flex流式布局。以一种更加优雅的方式实现元素布局。 轴的定义 在开始说它的特…

C语言#if、##ifdef、#ifndef的用法详解

假如现在要开发一个C语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同…

2023最新版easyrecovery数据恢复软件免费版测评

大家好,关于easyrecovery数据恢复软件免费版很多朋友都还不太明白,今天小编就来为大家分享关于easyrecovery数据恢复软件免费版下载使用的知识,希望对各位有所帮助! EasyRecovery其实是目前为止我用的最喜欢的一款数据恢复软件&a…

C++string的模拟实现(上篇)

目录 一.命名空间的封装与交换函数模板 1.命名空间的封装与类的定义 2.交换函数模板 二.string类的四个重要默认成员函数 1.构造函数的类外定义: 2.析构函数在类外的定义 3.拷贝构造函数在类外的定义 4.赋值运算符重载在类外的定义 5.关于两个string对象…

在线 OJ 项目(一) · 项目介绍 · 进程与线程 · 实现编译运行模块

一、项目介绍二、导入依赖、创建基本项目结构导入依赖创建基本项目结构三、进程、线程的基础知识回顾四、封装操作进程的工具类五、实现 “编译运行” 模块 Task 类六、封装读写文件的方法修改 JDK 版本七、Task 类的实现八、整理一下项目列表一、项目介绍 项目实现一个在线 O…

煤矿AI智能视频分析识别系统 opencv

煤矿AI智能视频分析识别系统通过opencvpython 深度学习网络模型,对皮带跑偏、撕裂、堆煤、异物、非法运人、有煤无煤状态等异常情况,以及人员工服穿戴、反光衣、安全帽、睡岗离岗、打电话、抽烟等行为进行自动抓拍存档。OpenCV基于C实现,同时…

【正点原子FPGA连载】第二十八章Linux并发与竞争 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南

1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第二十八章Linux…

【splishsplash】Houdini粒子的导入与导出

Houdini粒子的导入与导出 Houdini导入到splish 在Houdini中使用file导入任意几何模型 使用points from volume采样点,使其粒子化 使用file导出粒子化之后的模型,后缀写bhclassic 创建json场景文件(建议放到MyScences文件夹) …

C语言指针是什么?

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字…

HTTP协议 | 青训营笔记

1、概述 HTTP协议,超文本传输协议 应用层的协议,基于TCP协议,简单可扩展(可以自定义header) 每个HTTP请求都可以分为请求和响应两个部分 无状态的(不知道之前的请求是携带过什么信息) 2、协…

【Qt】8.QPainter、高级设置、手动调用绘图事件、绘图设备、文件操作、文件信息

目录 QPainter 代码 widget.h widget.cpp 结果 高级设置 代码 widget.h widget.cpp 结果 手动调用绘图事件 代码 widget.h widget.cpp 结果 绘图设备 代码 widget.h widget.cpp 结果 文件操作 代码 widget.cpp 结果 文件信息 代码 widget.cpp 结果…

一文带你看懂健康管理系统----IPMI

目录 1. IPMI概述 2. IPMI系统设计 3. 主BMC模块设计 5. 从IPMI模块设计 6. 名词解释 6. 代码 1. IPMI概述 智能平台管理接口(IPMI:Intelligent Platform Management Interface)是一项应用于服务器管理系统设计的标准,由Int…

BSN-DDC基础网络详解(一):基础介绍

BSN-DDC基础网络推出已经一年了,得到了行业应用方和广大开发者的高度认可。一年中BSN产品技术团队也在根据市场业务需求不断更新功能服务,我们将通过本系列文章为大家系统化介绍DDC网络的功能和使用,为感兴趣的朋友提供学习帮助。BSN-DDC基础…

历史与今日的事件

作者简介:一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 1.2023年1月31日中国最新量子计算机“悟空”即将问世&#xff0c…

【34】C语言 | 动态内存管理

目录 1.为什么存在动态内存分配 2、动态内存函数的介绍 2.1 malloc和free 2.2 calloc 2.3 realloc 1.为什么存在动态内存分配 我们已经掌握的内存开辟方式有: int val 20; //在栈空间上开辟四个字节 char arr[n] {0}; //在栈空间上开辟10个节的连续空间 但是上述的开辟…

mPEG-SS-NHS甲氧基聚乙二醇-二硫键-琥珀酰亚胺酯

mPEG-SS-NHS甲氧基聚乙二醇-双硫键-活性酯 名称:甲氧基聚乙二醇-双硫键-琥珀酰亚胺酯 英文名称:mPEG-SS-NHS 存储条件:-20C,避光,避湿 用 途:仅供科研实验使用,不用于诊治 外观: 固体或粘性…

【c语言进阶】动态通讯录

🚀write in front🚀 📜所属专栏: c语言学习 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我…

彭博:预订量未及预期,索尼大幅削减PS VR2首季订单

在索尼VR新品:PS VR2正式发货前夕,彭博社爆料称:因预订量不及预期,索尼已经大幅削减PS VR2首季订单量。消息人士称,索尼PS VR2发布后首个季度的订单目标是200万台,现已根据预订量减半,至约100万…

iOS 视频播放器开发

需求设计 做一个小学生教育辅导视频播放器。 参考小猿搜题视频播放器 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0GsyFSt-1675164972791)(https://tva1.sinaimg.cn/large/008vxvgGgy1h9xk4fm5xfj31sx0u0mz0.jpg)] [外链图片转存失败,源站可…