课堂笔记| 第七章:多态

news2024/10/7 18:20:05

本节课要点:

  • 继承
  • 特性
  • 多态
  • 虚函数

目录

一、多继承 

二、继承的前提:正确的分类 

三、多态

1. 虚函数

2. 确保覆盖和终止覆盖

3. 虚函数的实现原理

4. 虚析构函数 

四、纯虚函数和抽象类

1. 纯虚函数 

2. 抽象类 


一、多继承 

在之前的课程中,我们已经构建了自己的双向链表类。双向链表是一种线性表,线性表的作用是存储数据,故又称容器。学习了第六章:继承之后,我们会发现两者之间存在着Is-a关系,即双向链表是一种容器。因此,双向链表类应继承自容器类,即容器类是双向链表类的父类。 

下面考察 container 类(容器类) :

// container.h

#pragma once

#include "value_type.h"

struct container {
    using value_type = ::value_type;
    using pointer = value_type*;
    using reference = value_type&;
};

// traits
struct is_traversible {
    enum : bool {value = true}; // 枚举常量取的是bool值

    bool operator()() { // 这个重载使得结构体成为了可调用对象
        return value;
    }

    // 为了使用reference,把这段copy过来
    using value_type = ::value_type;
    using pointer = value_type*;
    using reference = value_type&;

    using callback = void (reference);
};

is_traversible 可遍历的,是所有容器共有的一种特性。

双向链表类继承容器类和特性类:

// dlist.h

...

class dlist : public container, public is_traversible { // 多继承
public:
    // typename container::value_type表明类型来自父类,typename指明这是一种类型
    using value_type = typename container::value_type;
    using pointer = typename container::pointer;
    using reference = typename container::reference;

private:
    // 定义回调函数类型
    // using callback = void (reference);
    using is_traversible::callback;

    ...

};

特性介于继承和混入(mix-in)之间,但在C++中采用继承的方式来实现。

 

二、继承的前提:正确的分类 

考虑雇员、经理、销售员三者之间的关系,以下是一种错误的分类:

// employee.h

#pragma once

#include <iostream>
#include <string>

class employee {
protected:
    job * j; // 赋值兼容原则

public:
    const std::string name;

    // 只能在初始化列表初始化常量,否则就变成了赋值
    employee(job * j, const std::string& n) : j(j), name(n) {}
    ~employee() {}

    void aboutme() {
        std::cout << name << ':' << j->title << std::endl;
    }
};

class manager : public employee {
    using employee::employee; // 继承构造函数
};

class salesperson : public employee {
    using employee::employee;
};

 修正后:

//employee.h

#pragma once

#include <iostream>
#include <string>

struct job {
    std::string title;
    int salary;
    job(const std::string & t, int s) : title(t), salary(s) {}
};

struct manager : public job {
    manager(int s = 10000) : job("manager", s) {}
};

struct salesperson : public job {
    salesperson(int s = 5000) : job("salesperson", s) {}
};

class employee {
protected:
    job * j;

public:
    const std::string name;

    employee(job * j, const std::string& n) : j(j), name(n) {}
    ~employee() {}

    void aboutme() {
        std::cout << name << ':' << j->title << std::endl;
    }
};

雇主和雇员是按照身份来分的,销售员和经理是按照职位来分的。

销售员和经理是一种角色(role),是可以互相转换的。而我们之前谈到的双向链表和数组是不会互相转换的,它们的关系铁铁的。

 

三、多态

回到之前的多边形类,我们试图求取各多边形的面积:

// poly.cpp

#include <iostream>

class poly {
protected:
    //共性
    std::string ids;
    size_t edge;

public:
    poly(std::string _ids = "poly", size_t e = 0) : ids(_ids), edge(e) {}
    ~poly() {}

    std::string what() const {
        return ids;
    }

    double area() const {
        return 0.0;
    }
};

class quad : public poly {
public:
    quad(std::string _ids = "quad") : poly(_ids, 4) {}
    ~quad() {}
};

class para : public quad {
protected:
    double width, height;

public:
    para(double w = 1, double h = 1, std::string _ids = "para") : quad(_ids), width(w), height(h) {}
    ~para() {}

    double area() const override {
        return width * height;
    }
};

class rect : public para {
public:
    rect(double w = 1, double h = 1, std::string _ids = "rect") : para(w, h, _ids) {}
    ~rect() {}
};

class square final : public rect {
public:
    square(double w = 1) : rect(w, w, "square") {}
    ~square() {}
};

int main() {
    quad *quads[] = { new para(7, 4), new rect(10, 5), new square(8) };

    for (auto q : quads) {
        std::cout << q->what() << ' ' << q->area() << std::endl;
        delete q;
    }

    return 0;
}

输出结果:

para 0
rect 0
square 0

据分析,这里的三个形体使用的都是其父类 quad 的 area() 方法。原因是:我们使用的是父类 quad 的指针指向的子类对象,导致内存被重解释了。quad 认为自己指向的就是一个 quad 对象,因此便调用了父类子对象的 area() 方法。

因此,我们需要使用子类的方法覆盖父类的同原型方法。 

1. 虚函数

若要使子类的成员函数能够覆盖父类的同原型成员,则必须将父类的该成员说明是虚函数。关键字 virtual 明确地告诉编译器:这是一个虚函数;该类子类中的同名版本将覆盖这个版本。 

class poly {
protected:
    std::string ids;
    size_t edge;

public:
    poly(std::string _ids = "poly", size_t e = 0) : ids(_ids), edge(e) {}
    ~poly() {}

    std::string what() const {
        return ids;
    }

    virtual double area() const {
        return 0.0;
    }
};

虚特性能够被继承。如果子类原型一致地重载了父类的某个虚函数,那么即使在子类中没有将这个函数显式说明成是虚的,它也会被编译器认为是虚函数。 

 

2. 确保覆盖和终止覆盖

被 override 描述符修饰的函数明确地告诉编译器,自己是一个覆盖版本。 

class para : public quad {
protected:
    double width, height;

public:
    para(double w = 1, double h = 1, std::string _ids = "para") : quad(_ids), width(w), height(h) {}
    ~para() {}

    double area() const override {
        return width * height;
    }
};

final 描述符能有效终止虚函数的覆盖,或者后继继承的发生。 

class square final : public rect {
public:
    square(double w = 1) : rect(w, w, "square") {}
    ~square() {}
};

 

3. 虚函数的实现原理

考察下述代码:

// sizeof-class-with-virtual.cpp

#include <iostream>

// alignof(类型) 向该类型对齐
class alignas(8) noVirtual { // align对齐 alignas(8)八字节对齐
    char a;
    void f() {}
};

// 按道理来说,函数是不占类对象大小的
class alignas(8) oneVirtual {
    char a;
    virtual void f() {}
};

class alignas(8) manyVirtual {
    char a;
    virtual void f() {}
    virtual int g() {
        return 0;
    }
    virtual double h(double) {
        return 1.0;
    }
};

int main() {
    std::cout << "size of noVirtual: " << sizeof(noVirtual) << std::endl;
    std::cout << "size of oneVirtual: " << sizeof(oneVirtual) << std::endl;
    std::cout << "size of manyVirtual: " << sizeof(manyVirtual) << std::endl;
    std::cout << "ref: size of pointer: " << sizeof(void *) << std::endl;
    return 0;
}

输出结果:

size of noVirtual: 8
size of oneVirtual: 16 # 这多出的8个字节是编译器干的
size of manyVirtual: 16
ref: size of pointer: 8 # 给出参考:指针的大小

为了实现多态,编译器首先要为每个多态类创建一张虚表(VTABLE),表中记录了这个类的所有虚函数的入口地址。此外,编译器还在每一个多态类的对象中设置了一个虚指针(Virtual Pointer/VPTR),它指向了该类的虚表(VTABLE)。

 

4. 虚析构函数 

考察下述代码:

//normal-destructor.cpp

#include <iostream>

class X {
public:
    X() {
        std::cout << "X()" << std::endl;
    }
    ~X() {
        std::cout << "~X()" << std::endl;
    }
};

class Y : public X {
public:
    Y() {
        std::cout << "Y()" << std::endl;
    }
    ~Y() {
        std::cout << "~Y()" << std::endl;
    }
};

int main() {
    X *p = new Y;
    delete p;
    return 0;
}

输出结果:

X()
Y()
~X()

问题:这个对象没有完全被拆除

解决:设置父类的析构函数为虚函数

因为子类和父类占据的内存不一致,因此父类的析构函数不能被继承。

修正后:

class X {
public:
    X() {
        std::cout << "X()" << std::endl;
    }
    virtual ~X() {
        std::cout << "~X()" << std::endl;
    }
};

 

四、纯虚函数和抽象类

1. 纯虚函数 

public:
    poly(std::string _ids = "poly", size_t e = 0) : ids(_ids), edge(e) {}
    ~poly() {}

    std::string what() const {
        return ids;
    }

    virtual double area() const = 0;
};

2. 抽象类 

// 狭义:只有类型定义和纯虚函数,广义:凡是拥有纯虚函数的类
struct container {
    using value_type = ::value_type;
    using pointer = value_type*; 
    using reference = value_type&;

    virtual bool empty() const = 0;
};

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

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

相关文章

SpringCloud Alibaba Sentinel实现熔断与限流

目录 一、简介 1.官网 & 介绍 2.下载地址 3.作用 4.如何使用 ⭐解决服务使用中的各种问题 5.Sentinel与Hystrix的区别 二、安装sentinel控制台 1.sentinel组件由2部分构成 2.安装步骤 ①地址 ②运行命令 ③访问sentinel界面 三、初始化演示工程 1.启动naco…

MyBatis 环境搭建配置全过程【IDEA】

文章目录一、MyBatis 介绍二、MyBatis 环境搭建1.MyBatis 下载2.配置 jdk 版本3.创建 Maven 工程4.IDEA 连接数据库5.项目文件构架6.引入相关依赖7.命令行创建数据库8.数据库配置文件9.核心配置文件三、入门测试程序1.创建表准备数据2.创建 POJO 实体3.创建映射文件4.修改核心配…

如何增加 KVM 虚拟机的磁盘大小

KVM 是一种集成到 Linux 内核中的虚拟化技术。您可以使用virsh、virt-manager和GNOME Boxes等工具创建虚拟机并与 KVM 交互。 磁盘空间不足是最常见的 VM 来宾问题之一。在测试新 VM 时,您可能会故意使用较小的磁盘。随着时间的推移,您会累积文件,直到虚拟磁盘几乎已满。以…

C语言笔记

fabs用来求double类型的绝对值&#xff0c;小数点后保留6位#include<math.h> double fabs(double ) labs用来求长整型long整型的绝对值&#xff0c; long cabs(long n); abs用来求整数的绝对值&#xff0c;labs求long long的绝对值#include<stdlib.h> double ret …

初识C++ (二)

初识C 二 上节课输入输出问题的一些补充一. 缺省参数1.1 半缺省参数1.2 全缺省参数二. 函数重载2.1 重载是什么意思&#xff1f;2.2 如何区分重载函数参数类型不同参数个数不同参数顺序不同附加题1附加题22.3 c支持函数重载的原理预处理编译汇编连接总结要以一种很认真的态度去…

深度优先搜索(dfs)和广度优先搜索(bfs)

目录 一、前言 二、关于dfs和bfs有意思的小故事 三、深搜题例 1、小猫爬山链接 2、基本思路 3、代码 &#xff08;1&#xff09;python代码 四、广搜题例 1、武士风度的牛链接 2、基本思路 3、代码 &#xff08;1&#xff09;C代码 &#xff08;3&#xff09;pyth…

现在的编程语言越来越多,为什么 C 和 C++ 还没有被现在的时代淘汰呢?

C/C会不会被时代淘汰&#xff1f;这个问题跳过了一步&#xff0c;关键是这个问题&#xff1a; C/C有哪些其它语言难以代替的特殊之处&#xff1f; 1、对实现细节的控制粒度 一般我们常说&#xff1a;C/C具有较高的执行效率。其实这句话不是特别准确&#xff0c;有时候它们并…

npm报错整理

npm报错整理一、代理1. 因为使用公司的镜像源导致的403 forbidden总结一、代理 1. 因为使用公司的镜像源导致的403 forbidden 在更新脚手架的时候&#xff0c;遇到了403的报错&#xff1a; 遇到问题不要怕&#xff0c;我们根据错误去解决就好。 &#xff08;1&#xff09;首…

【黄啊码】MySQL入门—13、悲观锁、乐观锁怎么用?什么是行锁、页锁和表锁?死锁了咋办?

大家好&#xff01;我是黄啊码&#xff0c;MySQL的入门篇已经讲到第12个课程了&#xff0c;今天我们继续讲讲大白篇系列——数据库锁 目录 从数据库管理的角度对锁进行划分 共享锁也叫读锁或 S 锁 排它锁也叫独占锁、写锁或 X 锁。 意向锁&#xff08;Intent Lock&#xf…

C++库——windows下使用Qt5.15.2+mingw64+msys2编译c++数学库GSL

文章目录准备配置msys2编译GSL准备 下载gsl库的源代码。大家可以到GSL的官网下载gsl的源代码。目前版本为2.7&#xff0c;下载完成后解压缩。 下载msys2。msys2是一套在windows上运行的用于构建库和程序的工具库&#xff0c;下载地址可以使用清华源的下载地址。下载完成后&…

【论文解读】伪装物体检测 Camouflaged Object Detection

文章目录伪装物体检测 Camouflaged Object DetectionSINet v1RF模块&#xff1a;PDC模块&#xff1a;SINet v2特征提取Texture Enhanced Module 纹理增强模块Neighbor Connection Decoder 邻居连接解码器Group-Reversal Attention 组反转注意力总结伪装物体检测 Camouflaged Ob…

计算机毕业设计之java+javaweb的烯烃厂压力管道管理平台

项目介绍 系统权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1b;管理员使用本系统涉到的功能主要有&#xff1a;主页、个人中心、通知公告管理、用户管理、管道信息管理、单位信息管理、管道统计信息管理等功能。 (b) 用户登录进入系统可以对主页、个人中心、通…

2022高频经典前端面试题(html+css+js上篇,含答案)

博主经历过多轮面试&#xff0c;因此想将自己的面试经验以及答题技巧&#xff0c;分享给即将面试找前端工作的同学。 2022高频经典前端面试题分为上中下三篇&#xff0c;分别会有html,css,js,es6,vue,ts,nodejs,以及hr面和反问面试官几个维度去进行&#xff0c;完整的还原面试场…

在 Linux 中使用 tcp 转储命令来分析网络

前言 Tcpdump是用于分析网络和查找相关网络问题的出色工具。它会在数据包经过时捕获数据包&#xff0c;并向您显示网络上正在发生的事情和传入情况。该命令的输出显示在 STDOUT 上&#xff0c;也可以存储在文件中。 感谢开发人员&#xff0c;他们将Tcpdump保留为开源项目。它…

LinkedIn最好工具-领英精灵有哪些批量加好友方法?

领英工具-领英精灵有哪些批量加好友方法 使用领英的人都会使用领英精灵&#xff0c;因为领英精灵是目前本土做得最好的领英工具&#xff0c;具有很多强大的功能。特别是拓展人脉方面&#xff0c;提供了很多批量加好友的方法。刚使用的新手可能不知道如何操作&#xff0c;下面就…

施耐德电气“创新开放日”走进中国软件研发中心 以软件与创新驱动产业“双转型”

来源 | 施耐德电气 2022年10月27日&#xff0c;施耐德电气在位于北京亦庄的中国软件研发中心举办“创新开放日”&#xff0c;充分展示其在中国深化研发的战略布局。当天&#xff0c;施耐德电气展示了该中心成立一周年以来的创新研发成果&#xff0c;并与合作伙伴共话软件发展趋…

【jsdoc-to-markdown】一步步实现js文件的文档生成

文章目录导读开发环境安装Vs code插件&#xff1a;Doxygen Documentation Generator效果优势jsdoc-to-markdown的使用了解 jsdocjsdoc-to-markdown安装创建测试文件example.jsjsdoc-to-markdown使用jsdoc-to-markdown踩坑&#xff01;&#xff01;&#xff01;参考资料导读 这个…

【C++】一文带你吃透string的模拟实现 (万字详解)

&#x1f308;欢迎来到C专栏~~ 模拟实现string (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort&#x1f393;&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&#x1f914;&#…

生态流量智能终端机 水电站生态流量多媒体智能终端-视频叠加、数据采集、远程传输

平升电子生态流量智能终端机 水电站生态流量多媒体智能终端是一款集人机交互、视频叠加、4G路由、数据采集、逻辑运算与远程传输功能于一体的多媒体智能终端设备。 此款产品为水电站生态流量监测项目的专用产品&#xff0c;便于监管单位及时掌握水电站的流量下泄情况&#xff…

【Django框架】——19 Django视图 01 路由配置

文章目录一、视图介绍二、路由配置1. 配置URLconf2.编辑项目中urls.py&#xff08;根路由&#xff09;3.创建应用中 urls.py (子路路由)4.路由文件urls.py5.API讲解一、视图介绍 视图就是应⽤用中views.py⽂文件中的函数 视图的第⼀个参数必须为HttpRequest对象&#xff0c;还…