C++之虚函数与多态

1、多态

前面三种称为静态绑定(静态多态),最后面的虚函数,则称为动态绑定(动态多态)。

2、静态绑定与动态绑定

要实现动态绑定,就必须使用虚函数

3、虚函数

只有当你在:基类的指针指向派生类的对象的时候,正常是调用基类的函数,当你需要掉用那个派生类的函数的时候,就把该函数声明为virtual。(前提是必须要在派生类中重写或者覆盖!!!)

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }
    virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Base::Fun3 .." << endl;
    }
};

class Dericed : public Base
{
public:
    virtual void Fun1()  // 等价于vioid Fun1,在派生类中,即使不加virtual,继承过来的也是虚函数
    {
        cout << "Dericed::Fun1 ..." << endl;
    }

    virtual void Fun2()
    {
        cout << "Dericed::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Dericed::Fun3 .." << endl;
    }
};

int main() {
    Base* p;
    Dericed d;

    p = &d;
    p->Fun1();   // 虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数
    p->Fun2();
    p->Fun3();   // 非虚函数,根据p指针实际类型来调用相应类的成员函数
    return 0;
}

// 输出
Dericed::Fun1 ...
Dericed::Fun2 ...
Base::Fun3 ..

4、虚析构函数

如果一个类要作为多态基类,要将析构函数定义成虚函数(不然析构的时候,子类不会被析构,存在内存泄露)
如果确定一个类不会被其他类继承,那就没必要定义成虚析构函数
#include <iostream>
using namespace std;

class Base
{
public:
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }
    virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Base::Fun3 .." << endl;
    }

    Base()
    {
        cout << "Base ..." << endl;
    }
    // 如果一个类要作为多态基类,要将析构函数定义成虚函数(不然析构的时候,子类不会被析构,存在内存泄露)
    // 如果确定一个类不会被其他类继承,那就没必要定义成虚函数
    virtual ~Base()
    {
        cout << "~Base ..." << endl;
    }
};

class Dericed : public Base
{
public:
    virtual void Fun1()  // 等价于vioid Fun1,在派生类中,即使不加virtual,继承过来的也是虚函数
    {
        cout << "Dericed::Fun1 ..." << endl;
    }

    virtual void Fun2()
    {
        cout << "Dericed::Fun2 ..." << endl;
    }

    void Fun3()
    {
        cout << "Dericed::Fun3 .." << endl;
    }

    Dericed()
    {
        cout << "Dericed() ..." << endl;
    }
    ~Dericed()
    {
        cout << "~Derived() ..." << endl;
    }
};

int main() {
    Base* p;
    p = new Dericed;

    p->Fun1();
    delete p;
    return 0;
}

//输出
Base ...
Dericed() ...
Dericed::Fun1 ...
~Derived() ...
~Base ...

5、虚表指针

如果一个类中有一个或者以上的虚函数,那么编译器会自动为这个类的头4个字节产生一个指针,指向虚表。

虚函数不能声明为静态函数的原因:

比如Base::Fun2(),他是直接访问,没有this指针,属于类共享的成员,不是类对象的一部分,就没有办法通过对象的头4个字节的虚表指针来找到虚表。同样的友元函数也不行。

图中下面的Base为Dericed

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }
    virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }

    int data1_;
};

class Dericed : public Base
{
public:
    virtual void Fun2()
    {
        cout << "Dericed::Fun2 ..." << endl;
    }

    virtual void Fun3()
    {
        cout << "Dericed::Fun3 ..." << endl;
    }

    int data2_;
};

typedef void (*FUNC)();
int main() {
    cout << sizeof(Base) << endl;
    cout << sizeof(Dericed) << endl;
    Base b;
    long** p = (long**)&b;
    FUNC fun = (FUNC)p[0][0];
    fun();
    fun = (FUNC)p[0][1];
    fun();
    cout << endl;

    Dericed d;
    p = (long**)&d;
    fun = (FUNC)p[0][0];
    fun();

    fun = (FUNC)p[0][1];
    fun();

    fun = (FUNC)p[0][2];
    fun();

    return 0;
}

6、object slicing与虚函数

对象在向上转型的时候会存在对象切割的问题,派生类特有的成员消失。

#include <iostream>
using namespace std;

class Object
{
public:
    virtual void Serialize()
    {
        cout << "Object::Serialize ..." << endl;
    }
};

class CDocument : public Object
{
public:
    virtual void Fun()
    {
        cout << "CDocument::Fun ..." << endl;
        Serialize();
    }

    virtual void Serialize()
    {
        cout << "CDocument::Serialize ..." << endl;
    }

    CDocument()
    {
        cout << "CDocument::CDocument() .." << endl;
    }

    CDocument(const CDocument& other)
    {
        cout << "CDocument(const CDocument& other)" << endl;
    }

    int data1_;
};

class CMyDoc : public CDocument
{
public:
    virtual void Serialize()
    {
        cout << "CMyDoc::Serialize ..." << endl;
    }

    int data2_;
};

int main() {
    CMyDoc mydoc;
    CMyDoc* pmydoc = new CMyDoc;

    cout << "#1 testing" << endl;
    mydoc.Fun();

    cout << "#2 testing" << endl;
    ((CDocument*)(&mydoc))->Fun();

    cout << "#3 testing" << endl;
    pmydoc->Fun();

    // 对象向上转型,CMyDoc中的Serialize被切割,所以调用的是上一层的Serialize
    // 会调用拷贝构造函数
    cout << "#4 testing" << endl;
    ((CDocument)(mydoc)).Fun();

    return 0;
}

//输出
CDocument::CDocument() ..
CDocument::CDocument() ..
#1 testing
CDocument::Fun ...
CMyDoc::Serialize ...
#2 testing
CDocument::Fun ...
CMyDoc::Serialize ...
#3 testing
CDocument::Fun ...
CMyDoc::Serialize ...
#4 testing
CDocument(const CDocument& other)
CDocument::Fun ...
CDocument::Serialize ...

7、overload重载、override覆盖、overwrite重定义或者重写

8、纯虚函数

虚函数的作用:当基类指针指向派生类对象的时候,在派生类中重写或者覆盖某个函数,则掉用的是派生类的虚函数。

这就使得我们可以以一致的观点来看待不同的派生类对象。

9、抽象类

拥有一个纯虚函数的类称为抽象类,抽象类不能实例化。

构造函数不能是虚函数的原因:如果构造函数是虚函数,那么就应该把这个构造函数放入vptr指向的虚函数表中;那么在构造函数还没调用完之前,这个对象是没有办法生成的,既然这个对象还没生成,就没办法知道这个虚表指针指向的地址。

一个类,如果作为一个多态用途的基类,析构函数就应该声明为虚函数。

#include <iostream>
using namespace std;

#include <vector>

class Shape
{
public:
    virtual void Draw() = 0;
    virtual ~Shape()  // Z这边必须要声明为虚析构函数,不然子类不会被释放(基类指针指向子类对象的时候,释放该指针的时候)
    {

    }
};

class Circle : public Shape
{
public:
    void Draw()
    {
        cout << "Circle::Draw() ..." << endl;
    }
    ~Circle()
    {
        cout << "~Circle() ..." << endl;
    }
};

class Square : public Shape
{
public:
    void Draw()
    {
        cout << "Square::Draw() ..." << endl;
    }
    ~Square()
    {
        cout << "~Square() ..." << endl;
    }
};

void DrawAllShapes(const vector<Shape*>& v)
{
    vector<Shape*>::const_iterator it;
    for (it = v.begin(); it != v.end(); ++it) {
        (*it)->Draw();  // 以一致的观点来看待所有的对象
    }

}

void DeleteAllShapes(const vector<Shape*>& v)
{
    vector<Shape*>::const_iterator it;
    for (it = v.begin(); it != v.end(); ++it) {
        delete(*it);
    }
}

int main() {
    //Shape s;  // ERROR:Variable type 'Shape' is an abstract class

    vector<Shape*> v;
    Shape* ps;
    ps = new Circle;
    v.push_back(ps);
    ps = new Square;
    v.push_back(ps);

    DrawAllShapes(v);
    DeleteAllShapes(v);
    return 0;
}

//输出
Circle::Draw() ...
Square::Draw() ...
~Circle() ...
~Square() ...

10、多态优点

11、虚析构函数

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理·考点知识点整理

根据往年考试题&#xff0c;对考点和知识点的一个整理。 校验编码 码距 一种编码的最小码距&#xff0c;其实就是指这种编码的码距。码距有两种定义&#xff1a; 码距所描述的对象含义 2 2 2 个特定的码其二进制表示中不同位的个数一种编码这种编码中任意 2 2 2 个合法编码的…

统一响应,自定义校验器,自定义异常,统一异常处理器

文章目录 1.基本准备&#xff08;构建一个SpringBoot模块&#xff09;1.在A_universal_solution模块下创建新的子模块unified-processing2.pom.xml引入基本依赖3.编写springboot启动类4.启动测试 2.统一响应处理1.首先定义一个响应枚举类 RespBeanEnum.java 每个枚举对象都有co…

JVMの垃圾回收

在上一篇中&#xff0c;介绍了JVM组件中的运行时数据区域&#xff0c;这一篇主要介绍垃圾回收器 JVM架构图&#xff1a; 1、垃圾回收概述 在第一篇中介绍JVM特点时&#xff0c;有提到过内存管理&#xff0c;即Java语言相对于C&#xff0c;C进行的优化&#xff0c;可以在适当的…

技术回眸一笑

回忆一下一年前的出差日记吧&#xff0c;那个时候真的是一点经验没有&#xff0c;干硬件又干软件&#xff0c;只能一步一步慢慢摸索&#xff0c;努力过后慢慢成长起来的吧。那个时候甚至开学了都没有正常报道&#xff0c;但是也收获了不少东西&#xff0c;并且也将作为我后来继…

Vue——组件数据传递与props校验

文章目录 前言组件数据传递的几种类型简单字符串类型数据专递其他类型数据(数字、数组、对象)传递注意事项 数据传递值校验限定数据类型 type给定默认值 default指定必选项 required 前言 组件与组件之间并不是完全独立的&#xff0c;他们之间可以进行一些数据的传递操作。传递…

10个高清视频素材库分享,高清高质量的分享给你

今天&#xff0c;我将为各位介绍几个极具价值的高清视频素材库。无论您是短视频创作者、自媒体运营者还是影视后期制作专家&#xff0c;这些素材库将大大提升您作品的质量。现在&#xff0c;让我们直接深入主题&#xff0c;探索这些优秀的资源平台&#xff01; 蛙学府视频素材…

使用cesiumLab使shp转为3dtlies

过程不做赘述&#xff0c;网上大把&#xff0c;说下注意事项。 1. 存储3DTiles 选项 若是打开则输出的文件为glb格式文件,因为glb文件好储存易传输跨平台。cesium可以使用但无法处理&#xff0c;例如改变颜色&#xff0c;改着色器等。若是不打开则输出的文件为bm3d格式文件,此…

ShowDoc item_id 未授权SQL注入漏洞复现

0x01 产品简介 ShowDoc 是一个开源的在线文档协作平台,它支持Markdown、图片等多种格式,方便团队成员共同编辑和分享文档。企业常见使用场景是使用其进行接口文档、内部知识库管理。 0x02 漏洞概述 2024年6月,ShowDoc官方发布新版本修复了一个SQL注入漏洞。鉴于该漏洞无前…

k8s怎么监听自定义资源的变更?(2)

接上一篇当生成下面代码之后怎么去使用呢&#xff1f; 1.生成crd文件 这里我们通过kubebuilder的一个子项目 controller-gen 来生成crd文件 https://github.com/kubernetes-sigs/controller-tools curl -L -o https://github.com/kubernetes-sigs/controller-tools; go ins…

【前端】响应式布局笔记——flex

二、Flex Flex(FlexiableBox:弹性盒子&#xff0c;用于弹性布局&#xff0c;配合rem处理尺寸的适配问题)。 1、flex-direction:子元素在父元素盒子中的排列方式。 父级元素添加&#xff1a;flex-direction: row; 父级元素添加&#xff1a;flex-direction: row-reverse; 父…

基于百度接口的实时流式语音识别系统

目录 基于百度接口的实时流式语音识别系统 1. 简介 2. 需求分析 3. 系统架构 4. 模块设计 4.1 音频输入模块 4.2 WebSocket通信模块 4.3 音频处理模块 4.4 结果处理模块 5. 接口设计 5.1 WebSocket接口 5.2 音频输入接口 6. 流程图 程序说明文档 1. 安装依赖 2.…

RPA-UiBot6.0数据采集机器人(海量信息一网打尽)内附RPA师资培训课程

前言 友友们是否曾为海量的数据信息而头疼,不知道如何从中精准抓取你所需的数据?小北的这篇博客将为你揭晓答案,让我们一起学习如何运用RPA数据采集机器人,轻松实现海量信息的快速抓取与整理,助力你的工作效率翻倍! 诚邀各位友友参与小北博客的评论,共同开启自动…

C++ Primer 总结索引 | 第十五章:面向对象程序设计

继承和动态绑定 对程序的编写 有两方面的影响&#xff1a;一是 我们可以更容易地定义与其他类相似 但不完全相同的新类&#xff1b;二是 在使用这些彼此相似的类编写程序时&#xff0c;我们可以在一定程度上 忽略掉它们的区别 在很多程序中都存在着一些相互关联 但是有细微差别…

【机器学习300问】107、自然语言处理(NLP)领域有哪些子任务?

自然语言处理&#xff08;NLP&#xff09;是计算机科学、人工智能和语言学领域的一个交叉学科&#xff0c;致力于让计算机能够理解、解析、生成和与人类的自然语言进行互动。自然语言指的是人们日常交流使用的语言&#xff0c;如英语、汉语等&#xff0c;与计算机编程语言相对。…

IO流----字节流

字节流 字节流&#xff1a;操作&#xff1a;文件字节输入输出流 &#xff1a;写入数据&#xff1a;读取数据&#xff1a;文件拷贝&#xff1a; 带缓冲区的字节输入输出流&#xff1a;拷贝文件&#xff1a;写入数据&#xff1a;读取数据: 深入 带缓冲区的字节输出流 &#xff1a…

C语言基础学习之位运算

枚举类型 enum 枚举名 { 枚举常量 //名字 }; 注意: 1.c语言中 对于枚举类型 实际上是 当作整型处理的 2.提高代码可读性&#xff0c; 写成枚举&#xff0c;可以做语法检查 3.枚举常量&#xff0c;之间用逗号隔开 4.枚举常量&#xff0c;可以给初值&#xff0c;给了初值之后&…

React(五)useEffect、useRef、useImperativeHandle、useLayoutEffect

(一)useEffect useEffect – React 中文文档 useEffect hook用于模拟以前的class组件的生命周期&#xff0c;但比原本的生命周期有着更强大的功能 1.类组件的生命周期 在类组件编程时&#xff0c;网络请求&#xff0c;订阅等操作都是在生命周期中完成 import React, { Com…

二叉树练习题(2024/6/5)

1翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,3,1]…

空间不够用了怎么办

空间告急啊哥们 整理一下清理空间有用的一些blog吧。 【linux】公共服务器如何清理过多的.cache缓存 linux根目录空间不足&#xff0c;追加空间到根目录下 【linux】linux磁盘空间 目录查看清理 和 文件查看清理

windows系统 flutter 开发环境配置

1、管理员运行powershell&#xff0c;安装&#xff1a;Chocolatey 工具&#xff0c;粘贴复制运行下列脚本: Chocolatey 官方安装文档 Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManage…