【C++进阶2--多态】面向对象三大特性之一,多种形态像魔法?

news2024/11/24 19:16:25

今天,带来C++多态的讲解。

多态和继承并用,能产生“魔法般的效果”。

*文中不足错漏之处望请斧正!


见见多态

是什么

使得父类指针或引用有多种形态。

怎么使它有多种形态呢?咱们先见见猪跑。

见见猪跑

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

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

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

int main()
{
    Base b;
    Derive1 d1;
    Derive2 d2;
    
    Base* ptr;
    ptr = &b;
    ptr->print();
    ptr = &d1;
    ptr->print();
    ptr = &d2;
    ptr->print();
    return 0;
}
Base
Derive1
Derive2

父类指针,存父类对象地址,就能调用父类中的print,存子类对象地址,就能调用子类中的print,拥有了多种形态。


多态的实现

满足多态的前提:子类对父类的虚函数完成重写。

啥是虚函数,啥是重写?

虚函数

虚函数是被virtual修饰的成员函数。可以理解为一种对函数体的泛化。

在声明虚函数的域,默认有一份实例;在此域之外,你可以对虚函数“实例化”,得到新的一份实例。

其作用是实现多态性。

重写/覆盖

重写就是“实例化”虚函数,可以产生新的“虚函数实例”,会把原来的实例覆盖掉。

  • 对虚函数重写的条件:[函数名、返回值、参数]和父类的虚函数相同
  • *重写的仅仅是函数体,重写前后接口是一样的
  • 例外
    • 子类的虚函数可以不写virtual
    • 协变:返回值可以是任意父子类关系的指针/引用
class Base
{
public:
    virtual void print() { cout << "Base" << endl;} 
};

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

class Derive2 : public Base
{
public:
    virtual void print() { cout << "Derive2" << endl;}
};
int main()
{
    Base b;
    Derive1 d1;
    Derive2 d2;
    
    Base* ptr;
    ptr = &b;
    ptr->print();
    ptr = &d1;
    ptr->print();
    ptr = &d2;
    ptr->print();
    return 0;
}
  • print这个虚函数默认有一份实例,它的函数体功能是打印"Base"的
  • print这个虚函数在Derive1中被重写了,这份新的实例把原来默认的覆盖了,它的函数体功能是打印”Derive1”的
  • print这个虚函数在Derive2中被重写了,这份新的实例把原来默认的覆盖了,它的函数体功能是打印”Derive2”的

如果不重写呢?

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

class Derive1 : public Base
{
public:
//    virtual void print() { cout << "Derive1" << endl;}
};

class Derive2 : public Base
{
public:
//    virtual void print() { cout << "Derive2" << endl;}
};

main函数不变,结果如下:

Base
Base
Base

看上面这个调用的手法,有个疑问:它是怎么知道调用哪个虚函数的实例的?

这就要说到虚函数表了。

虚函数表

有虚函数的类,其对象都会存一个虚函数表指针vptr,虚函数表vtable是干嘛的?

虚函数表是一个类的虚函数地址表,存放了这个类对某个虚函数的所有实例(对虚函数进行重写的到的真实函数)的地址。说白了,某个类对一个虚函数的实例,是用虚函数表来描述和组织起来的。

  • 为提高效率,这个可能高频访问的虚表(虚函数表)指针一般放在对象的头4/8个字节
  • 按我们的说法,虚函数表描述的是整个类对某个虚函数的实例,因此它就像类的static成员一样,属于整个类,所以存放在代码段

大概过程:

  1. 创建类对象(对象的头4/8个字节存了一个虚表指针)
  2. 调用对象的某个虚函数实例
  3. 根据虚表来找到当前类对这个虚函数的实例

普通调用和多态调用

普通调用也叫静态绑定,多态调用也叫动态绑定。

静态绑定:编译时通过调用方类型确定调用的函数
动态绑定:运行时通过父类指针指向的对象类型确定调用的函数

  • 用父类指针或引用调用被重写的虚函数是动态绑定
  • 其他都是静态绑定

在这里插入图片描述


多态中的析构函数

若在继承中出现这样的情况:

  1. 动态申请对象
  2. 子类没有重写析构函数(静态绑定)

则delete动态对象的空间时,析构调用不完全——只会根据指针类型静态绑定,只调用父类的析构。

class Base
{
public:
    ~Base()
    {
        cout << "~Base()" << endl;
        delete[] _pb;
    }
private:
    int* _pb = new int[10];
};

class Derive : public Base
{
public:
    ~Derive()
    {
        cout << "~Derive()" << endl;
        delete[] _pd;
    }
private:
    int* _pd = new int[20];
};

int main()
{
    Base* ptr = new Derive;
    delete ptr;
}
~Base()

可以看到,只会调用父类析构。

静态绑定不行,我们来动态绑定,多态上场。

class Base
{
public:
    virtual ~Base()
    {
        cout << "~Base()" << endl;
        delete[] _pb;
    }
private:
    int* _pb = new int[10];
};

class Derive : public Base
{
public:
    ~Derive() //此处可以不用写virtual,这是子类可以不用写virtual的一种使用场景
    {
        cout << "~Derive()" << endl;
        delete[] _pd;
    }
private:
    int* _pd = new int[20];
};

int main()
{
    Base* ptr = new Derive;
    delete ptr;
}
~Derive()
~Base()

满足了多态,调用完父类析构之后就会自动调用子类析构,成功解决。

Destructor

我们之前提到继承中的析构函数名都会被处理成Destructor,为了能够满足重写的条件。

在这里插入图片描述


继承中的对象模型

  1. 单继承(无重写)的虚函数表:
    1. 虚函数按照其声明顺序放于表中
    2. 父类的虚函数在子类的虚函数前面
  2. 单继承(有重写)的虚函数表
    1. 被重写的虚函数被放到了虚表中原来父类虚函数的位置(所以调用的时候不会跑去调用父类的,而是调用自己的)
    2. 没有被覆盖的函数依旧
  3. 多继承(无重写)的虚函数表
    1. 每个父类都有自己的虚表
    2. 子类的成员函数被放到了第一个父类的表中(所谓的第一个父类是按照声明顺序来判断的)

final和override

final

表明类不能被继承。

设计一个不能被继承的类

  1. final修饰类
class A final {};

class B : public A {};

int main()
{
    B b; //A被final修饰,无法被继承

    return 0;
}
  1. 构造私有化——子类想实例化必须调用父类构造。
class A
{
private:
    A() {}
};

class B : public A {};

int main()
{
    B b; //A的构造是私有,实例B需要调用A的析构,调不动,所以A无法被继承

    return 0;
}

override

override可以检查子类虚函数是否和父类的某个虚函数构成重写,或者强制要求某个函数被重写(需要重写的就都带上)。

//override:检查子类的虚函数是否完成重写
class Car
{
public:
    void Drive() {}
};

class Benz : public Car
{
public:
    //err:'Drive' marked 'override' but does not override any member functions
    virtual void Drive() override { cout << "Benz->comfortable" << endl; }
};

int main()
{
    Benz mycar;
    mycar.Drive();

    return 0;
}

抽象类

先导:纯虚函数

是什么:虚函数后写上0,不需要函数体的虚函数。(可以有函数体,但不会被执行)

virtual void func() = 0;

是什么

有纯虚函数的类。

为什么

用于接口继承。

  • 接口继承:主要是为了让子类重写,形成多态
  • 实现继承:主要是为了复用函数体
class Car
{
public:
    virtual void Drive() = 0;
};

class BMW : public Car
{
public:
    virtual void Drive()
    {
        cout << "驾驶乐趣+1" << endl;
    }
};

int main()
{
    BMW mycar;
    mycar.Drive();

    return 0;
}

特性

  • 抽象类不能实例化出对象
  • 若想实例化对象,必须重写纯虚函数(这样就不是抽象类了)
  • 子类继承了抽象类也还是抽象类

今天的分享就到这里了,感谢您能看到这里。

这里是培根的blog,期待与你共同进步!

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

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

相关文章

第05章_排序与分页

第05章_排序与分页 1. 排序数据 1.1 排序规则 使用 ORDER BY 子句排序 ASC&#xff08;ascend&#xff09;: 升序DESC&#xff08;descend&#xff09;:降序 ORDER BY 子句在SELECT语句的结尾。 1.2 单列排序 SELECT last_name, job_id, department_id, hire_date FROM …

Java基础学习(15)

Java基础学习 一、IO流进阶1.1 缓冲流1.1.1字节缓冲流1.1.2 字符缓冲流 1.2 转换流1.3 序列流1.4 反序列化流 /对象操作输入流1.4.1 序列化流/反序列化流的细节汇总 1.5 打印流1.5.1 字节打印流1.5.2 字符打印流 1.6 解压流、压缩流1.7 Commons-io1.8 hutool工具包 一、IO流进阶…

【C#】RemoveAt索引越界问题

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

使用FFMPEG和SDL2实现音视频同步的简易视频播放器

程序框架 由于之前都是针对FFMPEG某一个功能做的测试和学习。这里我简单做了一个视频播放器&#xff0c;并简单做了音视频同步。在此记录大致过程。 大致框架如下&#xff1a; 主线程 1.加载视频文件&#xff0c;查找音视频流信息 2.初始化音视频解码器 3.初始化SDL并设置…

题解校验码—CRC循环校验码与海明校验码

码距 一个编码系统的码距是任意两个码字的最小距离。 例如个编码系统采用三位长度的二进制编码&#xff0c;若该系统有四种编码分别为&#xff1a;000&#xff0c;011&#xff0c;100&#xff0c;111&#xff0c;此编码系统中000与111的码距为3&#xff1b;011与000的码距为2…

POE:性价比最高的 AI 整合网站

创作不易&#xff0c;如果本文对你有帮助&#xff0c;胖友记得一键三连 &#x1f62d;。更多 AI 优质内容推荐请关注主页 “AI” 专栏&#xff0c;笔者会不定期更新觉得自己用下来还不错的 AI 相关产品。 1.介绍 Poe 是一款同时整合了 ChatGPT、Sage、GPT-4、Claude、Claude-in…

经典神经网络(2)AlexNet及其在Fashion-MNIST数据集上的应用

2、深度卷积神经网络AlexNet ImageNet 数据集&#xff1a;一个开源的图片数据集&#xff0c;包含超过 1400万张图片和图片对应的标签&#xff0c;包含2万多个类别。 自从2010 年以来&#xff0c;ImageNet 每年举办一次比赛&#xff0c;即&#xff1a;ImageNet 大规模视觉识别挑…

数组排序算法

数组排序算法 一、冒泡排序算法二、直接选择排序三、插入排序四、反转排序 一、冒泡排序算法 冒泡排序算法&#xff1a; 类似气泡上涌的动作&#xff0c;会将数据在数组中从小到大或者从大到小不断向前移动。 基本思想&#xff1a; 冒泡排序的基本思想是对比相邻的两个元素值&…

并发编程(二) — 内存可见性问题

目录 前言 内存可见性问题 synchronized volatile CAS算法 CAS算法原理 CAS算法应用场景 CAS算法代码实现 参考目录 前言 在谈共享变量的内存可见性问题之前&#xff0c;先谈谈线程安全问题 &#xff0c;线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同…

c高级day4作业

有m1.txt m2.txt m3.txt m4.txt&#xff0c;分别创建出对应的目录&#xff0c;m1 m2 m3 m4 并把文件移动到对应的目录下使用break关键字打印九九乘法表&#xff0c;提示&#xff1a;printf "%d * %d %d" $i $j $((i*j)) #!/bin/bash for i in m1 m2 m3 m4 do#文件夹…

只限今日免费,Midjourney 5.1震撼更新!逼真到给跪,中国情侣细节惊艳,3D视频大片马上来

来源 | 新智元 微信号&#xff1a;AI-era 【导读】全新升级的Midjourney让全网又疯狂了&#xff0c;创造力解禁&#xff0c;出图更逼真。重要的是&#xff0c;限时免费到今天&#xff0c;要玩的抓紧了。 一个月前&#xff0c;Midjourney V5画的一对中国完美情侣在网上爆火&am…

涅槃重生,BitKeep如何闯出千万用户新起点

在全球&#xff0c;BitKeep钱包现在已经有超过千万用户在使用。 当我得知这个数据的时候&#xff0c;有些惊讶&#xff0c;也有点意料之中。关注BitKeep这几年&#xff0c;真心看得出这家公司的发展之迅速。还记得2018年他们推出第一个版本时&#xff0c;小而美&#xff0c;简洁…

python和pycharm的安装(安装包免费下载共享)

说明&#xff1a; 本文内容包括Python和Pycharm安装。 一、python安装&#xff1a; python是一门编程语言&#xff0c;安装python是为了能在电脑上使用这门语言。 1、python下载 下载链接&#xff1a;https://pan.baidu.com/s/1mWsJjO8HngNQtINCzu0bBA 提取码&#xff1a;9…

又又又发现了一个 AI 插件神器 TeamSmart

简介 TeamSmart AI 是最近比较火的 Chrome 插件&#xff0c;它是基于 ChatGPT 集成的 AI 助手团队工具 对&#xff0c;没错&#xff0c;是一个团队。这个团队里面有许多不同角色的成员&#xff0c;每隔成员都有自己的专业领域&#xff0c;比如商业、市场营销、灵魂写手、程序…

批量查看域名历史软件-网站老域名批量查询注册

未注册备案域名批量扫描软件 未注册备案域名批量扫描软件是专门用于批量扫描未备案的域名的一种工具。它可以快速识别未备案的域名&#xff0c;并帮助用户抓住还未被注册的值得备案的域名&#xff0c;以便用户及时注册备案并使用。 该软件主要具有以下几个优点&#xff1a; 高…

【小程序】输入框检验姓名、身份证(正则表达式)并提交

目标 输入绑定姓名、身份证号并进行校验若未填或校验不通过则显示绑定失败的轻提示若通过校验并提交则显示绑定成功 使用Vant Weapp (gitee.io)库。 思路与代码 html&#xff1a; wx:model绑定输入框输入的值data-key是一个属性&#xff0c;在js中的e.currentTarget.datase…

【新星计划-2023】ARP“攻击”与“欺骗”的原理讲解

网络管理员在网络维护阶段需要处理各种各样的故障&#xff0c;出现最多的就是网络通信问题。除物理原因外&#xff0c;这种现象一般是ARP攻击或ARP欺骗导致的。无论是ARP攻击还是ARP欺骗&#xff0c;它们都是通过伪造ARP应答来实现的。 一、ARP攻击原理 一般情况下&#xff0…

TypeScript语言编译命令

1. 安装 npm install -g typescript2. 编译 tsc工具是TypeScript编译器的控制台接口&#xff0c;它可以将TypeScript文件编译成JavaScript文件&#xff1b; 编译文件&#xff1a; tsc [options] [file ...]查看编译命令的帮助信息&#xff1a; tsc --help或者 tsc -h或者 tsc…

微服架构基础设施环境平台搭建 -(一)基础环境准备

微服架构基础设施环境平台搭建 -&#xff08;一&#xff09;基础环境准备 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、测试、发布的自动…

【Java AWT 图形界面编程】IntelliJ IDEA 乱码问题最佳配置方案 ( 配置文件编码 | 配置编译器编码参数 | 配置运行时编码参数 )

文章目录 一、IntelliJ IDEA 乱码问题二、IntelliJ IDEA 乱码问题最佳配置方案1、文件编码设置成 UTF-82、编译器编码参数设置成 UTF-83、 配置运行时编码参数为 GBK 一、IntelliJ IDEA 乱码问题 在 IntelliJ IDEA 中开发 AWT / Swing 图形界面程序 , 经常遇到乱码问题 ; 文件…