重学C++系列之多态

news2024/11/25 1:35:13

一、什么是多态

        多态是类的三大特性之一(封装,继承,多态)。多态是指由继承而产生的相关不同的类,其派生类对象对同一个消息会作出不同响应。

二、引入多态的目的

        能增加程序的灵活性,可以减轻系统升级和维护,调试的工作量和复杂度就大大降低,程序可拓展性好。

三、怎么实现多态

        多态其实可以分为静态多态(静态编译,编译时的多态性)和 动态多态(动态绑定,运行时的多态性)。静态多态主要是函数重载,动态多态主要是抽象类的多态。以下篇幅讲动态多态。

        1、实现多态的前提是必须有继承,并且基类中要有虚函数

        2、派生类中要有虚函数重写

        3、派生类对象的地址赋值给基类的指针,通过基类的指针或者基类的引用

四、多态的其他名词解释

        1、虚函数

        虚函数是用关键字(virtual)修饰的函数,通常出现在基类中,要实现多态,在派生类中必须重写基类的虚函数。基本语法如下:

// 虚函数
virtual 数据类型 函数名(数据类型 形参...)
{
    // 函数体
}

        2、纯虚函数

        虚函数的升级版,使用0去给虚函数赋值。纯虚函数是没有函数体的,所以,它就不能被调用,为了防止它被调用,将有纯虚函数的类变为抽象类,语法如下:

virtual 数据类型 函数名() = 0; // 后面必须是0,不能是1或者其它

        3、抽象类

        拥有纯虚函数的类为抽象类。抽象类的特点是不能被实例化,但是,还是可以声明指针或者引用来使用。

        4、虚析构函数

        将基类的析构函数设置为虚函数,就是虚析构函数。多态的实现是采用基类指针或者基类引用,在销毁对象时,采用delete方式,此时由于指针是基类的,所以只会释放基类对象,不会释放派生类对象,造成内存泄漏。因此,引入虚析构函数来解决这个问题。基本语法如下:

class Base
{

 public:
    // 虚析构函数,前面直接加关键字virtual
    virtual ~Base()
    {

    }
}

五、多态与虚继承的对比

        多态原理实现过程:

        1、在类设计时,使用关键字(virtual)来声明函数为虚函数,目的是告诉编译器该函数要进行晚捆绑(运行时多态)。

        2、编译器在编译时,就准备好虚表(vtable)和 虚指针(vptr)。

        3、执行晚捆绑(发生在程序运行时),通过实际的对象来使用虚指针去查虚表。

        4、在虚表中,查找到该函数则执行。

        虚继承实现过程:

        1、在类进行继承时,使用关键字(virtual)来声明为虚继承,目的是告诉编译器要进行虚继承。

        2、编译器在编译时,就将虚基类空间只保留一份,并且准备好虚表(vtable)和虚指针(vptr)。

        3、在访问虚基类中成员时,也要查表,虚指针和虚表由编译器来进行维护。

        4、对于虚基类中的成员,任何对象对它进行修改,数据都会发生改变。

六、案例

        1、证明虚指针

#include <iostream>


using namespace std;

class Base
{

public:
    Base()
    {
        cout << "Base()" << endl;

    }
    virtual void func() = 0;    // 纯虚函数
   
};


int main()
{
    // Base类中没有任何成员变量,但是有一个纯虚函数,占一个指针空间
    cout << "sizeof(Base): " << sizeof(Base) << endl;
    // 在64位空间中一个指针占8个字节,在32位空间中一个指针占4个字节
    cout << "sizeof(int*): " << sizeof(int*) << endl;

    return 0;
}

        2、多态的简单使用

#include <iostream>

using namespace std;


class Animal
{
public:
    // 虚函数
    virtual void eat()
    {
        cout << "Animal::eat()" << endl;
    }
};


class Dog:public Animal
{
public:
    // 重写虚函数,virtual 关键字可加可不加,推荐不加,因为编译器最终都会自动加上
    void eat() override      // override关键字作用是用来说明该函数的特性,提高程序的可读性
    {
        cout << "Dog::eat()" << endl;
    }
};


class Cat:public Animal
{
public:
    void eat() override
    {
        cout << "Cat::eat()" << endl;
    }
};


int main()
{
    Animal* p[3];   
    // 使用基类指针或者基类引用来调用多态
    p[0] = new Animal;  
    p[1] = new Dog;
    p[2] = new Cat;
    p[0]->eat();
    p[1]->eat();
    p[2]->eat();



    return 0;
}

        3、纯虚函数的使用

#include <iostream>

using namespace std;

// 抽象类
class Animal
{
public:
    // 纯虚函数
    virtual void eat() = 0;
    
};


class Dog:public Animal
{
public:
    // 重写虚函数,virtual 关键字可加可不加,推荐不加,因为编译器最终都会自动加上
    void eat() override      // override关键字作用是用来说明该函数的特性,提高程序的可读性
    {
        cout << "Dog::eat()" << endl;
    }
};


class Cat:public Animal
{
public:
    void eat() override
    {
        cout << "Cat::eat()" << endl;
    }
};


int main()
{
    // Animal animal; // 抽象类不能实例化
    Dog dog;
    Cat cat;
    // 使用基类引用来调用纯虚函数
    Animal &p1 = dog;   
    Animal &p2 = cat;
    p1.eat();
    p2.eat();


    return 0;
}

        4、虚析构函数的使用

        (1)没有声明虚析构函数

#include <iostream>


using namespace std;

class Base
{

public:
    Base()
    {
        cout << "Base()" << endl;

    }
    virtual void func() = 0;    // 纯虚函数
    ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Test:public Base
{
private:
    int *p;
public:
    Test(int len = 1)
    {
        cout << "Test()" << endl;
        p = new int[len];
    }
    ~Test()
    {
        cout << "~Test()" << endl;
        delete []p;
    }
    void func() override
    {
        cout << "Test::func()" << endl;
    }
    
};


int main()
{
    Base *p = new Test(5);  // 基类指针使用派生类对象

    delete p;   // 没有使用虚析构函数,只会调用基类的析构函数

    return 0;
}

        (2)声明虚析构函数

#include <iostream>


using namespace std;

class Base
{

public:
    Base()
    {
        cout << "Base()" << endl;

    }
    virtual void func() = 0;    // 纯虚函数
    virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Test:public Base
{
private:
    int *p;
public:
    Test(int len = 1)
    {
        cout << "Test()" << endl;
        p = new int[len];
    }
    ~Test()
    {
        cout << "~Test()" << endl;
        delete []p;
    }
    void func() override
    {
        cout << "Test::func()" << endl;
    }
    
};


int main()
{
    Base *p = new Test(5);  // 基类指针使用派生类对象

    delete p;   // 使用虚析构函数,会调用派生类的析构函数

    return 0;
}

七、总结

        多态是C++类三大特性之一,多态可以分为编译时的多态和运行时的多态,实现运行时的多态需要有抽象类,并且抽象类用指针或者引用指向派生类对象。建设将析构函数改写成虚析构函数。注意区分虚继承和多态中的虚函数。

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

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

相关文章

EC200U-CN学习(三)

EC200U系列内置丰富的网络协议&#xff0c;集成多个工业标准接口&#xff0c;并支持多种驱动和软件功能&#xff08;适用于Windows 7/8/8.1/10、Linux和Android等操作系统下的USB驱动&#xff09;&#xff0c;极大地拓展了其在M2M领域的应用范围&#xff0c;如POS、POC、ETC、共…

TCP协议如何实现可靠传输

TCP最主要的特点 TCP是面向连接的运输层协议&#xff0c;在无连接的、不可靠的IP网络服务基础之上提供可靠交付的服务。为此&#xff0c;在IP的数据报服务基础之上&#xff0c;增加了保证可靠性的一系列措施。 TCP最主要的特点&#xff1a; TCP是面向连接的输出层协议 每一条…

vue3.2 + elementPlus + Windi CSS + ts创建一个好用的可兼容不同宽高的login页面

1.效果预览 2. 代码准备 导入windiCSS&#xff1a; npm i -D vite-plugin-windicss windicss windiCSS官网&#xff1a; https://cn.windicss.org/integrations/vite.html 使用vite创建好你的vue工程 sass版本为&#xff1a; 1.49.9 3.Windi CSS在页面中使用 apply 二次定义类名…

ACwing 1081. 度的数量

文章目录 题意思路代码 题意 给你一段区间[x, y]求其中满足一个数恰好等于K个互不相等的B的整数次幂之和的数的个数。 例如&#xff1a;x 15, y 20, k 2, b 2&#xff0c;那么对于这个区间有且仅有三个数满足题意&#xff1a; 17 2 4 2 0 10001 17 2^42^0 10001 1724…

行为型设计模式之策略模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

OpenCV实现高斯模糊加水印

# coding:utf-8 # Email: wangguisendonews.com # Time: 2023/4/21 10:07 # File: utils.pyimport cv2 import PIL from PIL import Image import numpy as np from watermarker.marker import add_mark, im_add_mark import matplotlib.pyplot as plt# PIL Image转换成OpenCV格…

Docker 全栈体系(六)

Docker 体系&#xff08;高级篇&#xff09; 三、Docker微服务实战 1. 通过IDEA新建一个普通微服务模块 建Module docker_boot 改POM <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" …

【C#】医学实验室云LIS检验信息系统源码 采用B/S架构

基于B/S架构的医学实验室云LIS检验信息系统&#xff0c;整个系统的运行基于WEB层面&#xff0c;只需要在对应的工作台安装一个浏览器软件有外网即可访问&#xff0c;技术架构&#xff1a;Asp.NET CORE 3.1 MVC SQLserver Redis等。 一、系统概况 本系统是将各种生化、免疫、…

当ChatGPT应用在汽车行业,具体有哪些场景?

​ ChatGPT有潜力彻底改变汽车行业并将其提升到新的高度。在ChatGPT的加持下&#xff0c;该行业的多个领域都将取得重大变化。 利用ChatGPT作更高级的虚拟助理 你可能用过现有的虚拟助理&#xff0c;它们一系列的回复有时候让人不得不感叹一句“人工智障”&#xff01;然而&a…

【12】STM32·HAL库开发-STM32时钟系统 | F1/F4/F7时钟树 | 配置系统时钟

目录 1.认识时钟树&#xff08;掌握&#xff09;1.1什么是时钟&#xff1f;1.2认识时钟树&#xff08;F1&#xff09;1.2.1STM32F103时钟树简图1.2.2STM32CubeMX时钟树&#xff08;F103&#xff09; 1.3认识时钟树&#xff08;F4&#xff09;1.3.1F407时钟树1.3.2F429时钟树1.3…

【C++】解决菱形继承而产生的虚基表(偏移量表)

文章目录 继承概念切片和重定义派生类的默认成员函数菱形虚拟继承 继承概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派…

LeetCode91.Decode-Ways<解码方法>

题目&#xff1a; 思路&#xff1a; 关键在于有没有0,其次能不能二位.二位的要求是在 1-- 26的范围内.所以动态规划的时候需要限制. 代码是&#xff1a; //codeclass Solution { public:int numDecodings(string s) {int n s.size();vector<int> dp(n1, 0); // 定义状…

java使用htmlunit + jsoup 爬网站图片案例(爬虫学习)

申明 该文章用于自己学习爬虫使用 案例分析 目的: 从百度图片中搜索"风景"并下载图片到本地 思路: 使用htmlunit进行模拟用户操作, 并使用jsoup对数据进行解析,获取到需要的数据后,再下载到本地保存 htmlunit官网 jsoup官网 操作步骤 使用谷歌浏览器打开百度图片…

用html+javascript打造公文一键排版系统8:附件及标题排版

最近工作有点忙&#xff0c;所 以没能及时完善公文一键排版系统&#xff0c;现在只好熬夜更新一下。 有时公文有包括附件&#xff0c;招照公文排版规范&#xff1a; 附件应当另面编排&#xff0c;并在版记之前&#xff0c;与公文正文一起装订。“附件”二字及附件顺序号用3号黑…

Mysql适用于初学者的前期入门资料

文章目录 前言一、SQL语句分类二、SQL语句的书写规范三.数据库操作四、MySQL字符集1、问题① 五、UTF8和UTF8MB4的区别六、数据库对象七、数据类型八、表的基本创建1、创建表2、查看表3、删除表4、修改表结构5、复制表的结构 九、数据库字典十、表的约束1、非空约束(NOT NULL)2…

matplotlib从起点出发(4)_Tutorial_4_Lifecycle

1 一幅图像的生命周期 本教程旨在揭示使用matplotlib绘制的一幅图像的生命周期&#xff0c;包括它的开始、中间和结束。我们将从一些原始数据开始&#xff0c;最后保存自定义可视化的图形。在此过程中&#xff0c;我们尝试使用matplotlib突出一些简洁的功能和最佳实践。 2 关…

【Matlab】基于粒子群优化算法优化BP神经网络的时间序列预测(Excel可直接替换数据)

【Matlab】基于粒子群优化算法优化BP神经网络的时间序列预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.数学公式3.文件结构4.Excel数据5.分块代码5.1 fun.m5.2 main.m 6.完整代码6.1 fun.m6.2 main.m 7.运行结果 1.模型原理 基于粒子群优化算法&#xff08;Pa…

【LeetCode 75】第九题(443)压缩字符串

目录 题目: 示例: 分析: 题目: 示例: 分析: 给一个字符串,如果该字符有连续的相同的字符,则只保留一个字符,并在该字符后加上该字符连续的数量.例如原数组为 [a,a,a],则因为字符a连续了三次,因此可以压缩为[a,3],我们需要注意的是数字也需要是字符,则如果字符连续次数不止有…

SpringCloud学习路线(13)——分布式搜索ElasticSeach集群

前言 单机ES做数据存储&#xff0c;必然面临两个问题&#xff1a;海量数据的存储&#xff0c;单点故障。 如何解决这两个问题&#xff1f; 海量数据的存储问题&#xff1a; 将索引库从逻辑上拆分为N个分片&#xff08;shard&#xff09;&#xff0c;存储到多个节点。单点故障…

C++笔记之memset分析

C笔记之memset分析 code review! 文章目录 C\笔记之memset分析1.介绍2.误区总结3.代码一&#xff0c;char数组和uint8_t使用memset4.代码三&#xff0c;int数组使用memset 1.介绍 2.误区总结 参考文章&#xff1a;Cmemset踩坑 3.代码一&#xff0c;char数组和uint8_t使用mem…