首页 > 其他分享 >浅谈面向对象三大特性(着重介绍虚函数实现的多态)

浅谈面向对象三大特性(着重介绍虚函数实现的多态)

时间:2024-08-21 22:52:14浏览次数:11  
标签:虚表 函数 show 多态 指针 class 三大 浅谈

       写在前面:文章内容分享为主,如有不当之处,恳请批评指正。

        今天在使用C++的工厂模式的时候,突然发下有些生疏,就想着发一篇博客,巩固一下,但突然想到工厂模式中设计的继承以及多态的特性,决定先发一篇有关于C++多态的文章,其他的就丢给明天吧!

一、面向对象三大特性:

        首先说一下面向对象的三大特性:继承、封装、多态。其实这三种特性的思想在我们日常生活中也很常见,很多地方都有这三种思想的运用,不一定是在编程的时候。首先说一下封装,我们先看一下封装的介绍:

1. 封装 (Encapsulation)

        封装是指将数据和操作数据的方法结合在一起,形成一个独立的单元(即对象),并隐藏对象的内部细节,对外只暴露必要的接口。这种隐藏内部实现细节、保护数据不被随意访问和修改的方式,增强了代码的安全性和可维护性。

        这是官方的说法,很好理解,但在日常的学习过程中,我更倾向于去想问什么这么做,然后结合自己的经历来理解。我们每天拿在手里的手机其实就是一种封装的思想,手机封装了内部的结构,你不必知道它内部用了多少晶体管,多少电阻,多少电容,也不必关心他们是串联还是并联,我们只需要按一下开机键,就可以畅通无阻的使用,这实际上就是封装的思想,隐藏了内部的细节,避免我们对电路进行不当的操作。

2. 继承 (Inheritance)

        继承是指一个类可以基于另一个类(称为父类或基类)的特性来创建,这样新类(称为子类或派生类)可以继承父类的属性和方法,并可以在此基础上进行扩展或修改。继承允许代码的重用,并且通过继承可以实现类与类之间的层次结构。

        这个我没想出来生活中的应用,但是很好理解,你可以理解为你可以继承你父亲的财产,你可以调用这些财产,你父亲定义这份财产为一个买房的方法,而你用这些钱来买彩票,这是一种继承+多态的行为,哈哈,开个玩笑,当然这么说不好,这也许是程序员的冷幽默。

3. 多态 (Polymorphism)

        多态性是指相同的操作在不同对象上可以有不同的表现形式。多态性主要体现在方法重载(同一个类中相同方法名但参数不同)和方法重写(子类可以重写父类的方法)。多态使得代码更加灵活,可以根据不同的对象执行不同的行为。

        还是上面说的手机的例子,想想我们的手机,现在是不是一般都只有一个接口?这个接口你可以用来插耳机,插充电器,还可以用来传数据,为什么一个接口可以实现这么多功能,为什么我们看不到实现的原理呢?这就像我们通过虚函数实现的多态,封装在class里,我们也不知道怎么实现,我们只是调用了相应的接口,(后面会讲到),我们不必知道是怎么实现的,只需要使用,剩下的交给手机内部就好了!现在想想以前的手机有那么多接口,圆圆的插耳机的,方方的插充电线的,一定是因为不知道面向对象的多态~!(实际我也不了解哈,也有可能是因为技术的瓶颈,不能太武断,哈哈哈)。

二、多态的实现

        1.虚函数

        谈完了上面的面向对象的三大特性,下面我们来说说实际的操作。

        上面我们已经讲过,多态其实就是一种接口,调用不同的是实现方式。C++中的多态主要通过两种方式来实现,一种是操作符重载,在这里并不详细讲述,(我们在设计一个class的时候,应该本着要设计出和编写标准库的大师一样方便好用的class,当然这是很难很难很难的,但一个好的class,一定绕不开操作符的重载,过后我会发一篇设计Class相关的博客,来详细阐述运算符重载类型的多态)另外一种就是通过虚函数实现的多态。

        首先先提问:什么是虚函数?

        虚函数是在基类之中声明的一个函数,这个函数我们可以在派生类中对他进行重写。当我们生命一个基类的指针,指向一个派生类实例的时候,我们可以通过基类指针调用派生类中重写的方法,虚函数的这种特性允许程序根据对象的实际类型动态决定需要调用哪个版本的函数。如此便实现了多态。

class Base {
public:
    virtual void show() {
        cout << "Base class show()" << endl;
    }
};

        上述中的show函数,即为一个虚函数,并且基类给出了自己的定义,即输出一个字符串“Base class show()”,表示调用的为基类的show方法。

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show()" << endl;
    }
};

        接下来我们声明了一个派生类Derived,pubilc继承Base,并且对show方法进行的重写,输出一个字符串“Derived class show()”,代表调用的为派生类的show方法。

int main() {
    Base* base_class_pointer;
    Derived derived_class_instance;
    base_class_pointer = &derived_class_instance;

    // 调用的将是Derived类中的show()函数,而不是Base类中的show()函数
    base_class_pointer->show();  // 输出:Derived class show()

    return 0;
}

        在main函数中,我们实例化了一个派生类,并初始化了一个基类类型的指针,指向这个派生类,然后通过指针调用show方法,输出的结果为Derived class show();也就是说此时调用的是派生类的方法。那么可能有些人心里会有疑惑,为什么如此顺理成章地就成功了呢?为什么我基类类型的指针可以调用派生类的方法呢?反过来为什么不行呢?

         下面让我讲述一下指针的一些机制,请看下图!哈哈哈,画工比较抽象。

        我们可以这么来看,首先int型占4个字节,int_pointer是一个int型的指针,那么他就会先找到存储在自己内存空间内的地址,即a元素的首地址0x0001,然后往后偏移四个字节(因为int占4字节),如此做后,读取这片内存的数据,然后按照int的读取方式,就可以得到正确的数值。那我们在类中进行读取也是如此,一般情况下,派生类占用的内存都比基类的内存大,那如果我们声明基类的指针指向派生类,也就是下面这种情况。

        哈哈,这样的话就比较好理解了吧,你的钥匙只能插在你自己家的大门上,不可以插在邻居家的大门上。这也是为什么我们需要用一个基类类型的指针,指向派生类来调用方法,而反过来是不允许的。

        如果你声明一个类的时候,你完全不知道该怎么设计,甚至你不知道你定义的这个方法该怎么实现,这种情况下,你可以使用纯虚函数,即告诉子类,请一定要重写这个方法,因为我根本不知道该怎么做!(就好像老爸对你说:你老爸我没开上劳斯莱斯,你继承了我,然后替我实现这个梦想吧!)声明了纯虚函数的类,一定是抽象基类,即无法被实例化的类,因为类中有没定义好的方法,怎么实例化呢?

class AbstractBase {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
};

这是纯虚函数的声明,即在()后面加上=0即可;此时你就拥有了一个抽象基类。

2.虚表

        下面让我来讲讲虚表,即C++用于支持多态的一个内部数据结构。虚表内存储了类的所有虚函数的指针,由此使得程序在运行阶段能够根据对象的实际类型调用正确的函数。虚表是与类相关联的,而不是与具体的对象(即类的实例),说到这里你可能已经想到,很像static呀,没错,虚表也是存储在程序的静态区域之中,而不是在对象的具体内存,不同的对象,共享同一类的虚表

        那么对象是怎么和远在静态区的虚表联系起来的呢?答案是虚指针,在每个对象的内存中,都包含一个虚指针,这个指针指向对象的实际类型关联的虚表,虚指针是对象的一部分,但是我们在sizeof查看一个类的大小时,往往会发现,并没有算进去虚指针的内存,这是因为虚指针是由编译器自动管理的,他是一种内部的机制。

        当编译器处理一个含有虚函数的类时,他会为该类创建一个虚表。虚表之中,存储了所有虚函数的地址。这个虚表在程序加载到内存是创建,并在运行时静态存储。也就是说,还在编译器的时候,虚表就已经建立了。在后续运行期我们创建对象时,编译器会自动初始化虚指针,指向相应的虚表,来进行虚函数的调用,函数调用这个过程是在运行期实现的,是一个动态的过程,程序动态的决定实际调用哪个版本的函数,这也就是动态绑定

        到此为止,C++通过虚函数实现的多态就到这里了,如果对虚表更具体的实现感兴趣的,可以看看深度探索C++对象模型这本书,在github上就可以找到,在里面更加详细的讲解了这部分的内容。

        写在后面:如有不当,恳请批评指正!!!!!

标签:虚表,函数,show,多态,指针,class,三大,浅谈
From: https://blog.csdn.net/qq_52392487/article/details/141402566

相关文章

  • SAP B1 三大基本表单标准功能介绍-业务伙伴主数据(三)
    背景在SAPB1中,科目表、业务伙伴主数据、物料主数据被称为三大基本表单,其中的标准功能是实施项目的基础。本系列文章将逐一介绍三大基本表单各个字段的含义、须填内容、功能等内容。附上SAPB110.0的帮助文档:SAPBusinessOne10.0|SAPHelpPortal本文介绍的是:业务......
  • 基础知识|C++|封装、继承、多态
    一、封装、继承、多态是什么封装:将具体实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性,使类成为一个具有内部数据的自我隐藏能力、功能独立的软件模块。意义:保护或防止代码在无意之中被破坏,保护类中的成员,不让类中以外的程序直接访问或者修改,只能通过提供的公......
  • 浅谈HTML
    html是一种标签语言,用来写前端页面的,通常结合CSS和js来写。主要用于web开发,B/S架构的系统,所谓B/S其实也是一种特殊的C/S,只不过此时浏览器变成了客户端。B/S架构:B是browser,S是serverC/S架构:C是client,S是server**什么是HTML?**HTML是用来描述网页的一种语言。HTML指的是超......
  • Python--面向对象编程:封装、继承和多态
    在面向对象编程(OOP)中,封装、继承和多态是三个核心的概念,掌握它们有助于更好地设计和开发复杂的软件系统。以下是对这三个概念的详细介绍:1.封装(Encapsulation)封装指的是将对象的状态(属性)和行为(方法)隐藏在对象内部,不暴露给外界。外界只能通过对象提供的接口(即公开的方法)来访问......
  • 浅谈Java Spring Boot
    一、基本介绍SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,SpringBoot致力于在蓬勃发展的快速应用开发领域(rapidapplicatio......
  • 浅谈 Java Spring框架
    一、基本介绍Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。二、核心特性依......
  • 20240820(周二)AH股行情总结:A股三大指数收跌近1%,游戏传媒板块大涨,工行超中国移动成市值
    A股三大股指集体下挫,创业板指跌1.34%。国债期货收盘多数上涨,30年期主力合约涨0.22%。工商银行股价再创历史新高,盘中市值超过中国移动。“黑神话”概念股大涨,浙版传媒涨停,华谊兄弟涨超10%,新迅达20CM涨停。周二,A股三大指数均收跌近1%受《黑神话:悟空》大热带动,A股游戏、传媒板......
  • 浅谈数据类型(C语言)
            前言     本篇是我学术系列第一篇讲解,如有纰漏,还请多多指教。本篇将在C语言背景下,VS2019环境下,简要说明C语言数据类型。        数据类型的种类     干货直接上:C语言数据类型定义方式符号类型关键字内置类型有符号(signe......
  • TCPIP路由技术第一卷 第三大部分-2 重分步的定义及实验
    tcp/ip路由控制案例研究1单向重分布和双向重分布1.什么是充分:从一个协议(或者进程域)学习到的路由(以及运行该协议的直连接口)重分布到另一个协议的数据库中2.度量值,重分布时需要制定度量值让多种协议可以理解原来的度量值(seedmetric)3.ad(管理距离),从多个协议学习到同一......
  • TCPIP路由技术第一卷 第三大部分-4 路由更新Distribute-list
    外部的路由可以进入到路由表中,路由表中的路由也可以被通告出去,那么路由过滤器正是通过管制这些出入路由表的路由来工作的.distributelisteigrpoutin方向完全满足ospfout方向不行r1:routerripnoautoversion2network12.0.0.0r2:routerripnoautoversion2ne......