C++之虚函数与多态

news2024/11/19 19:21:08

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、虚析构函数

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

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

相关文章

Mysql 常用命令 详细大全【分步详解】

1、启动和停止MySQL服务 // 暂停服务 默认 80 net stop mysql80// 启动服务 net start mysql80// 任意地方启动 mysql 客户端的连接 mysql -u root -p 2、输入密码 3、数据库 4、DDL&#xff08;Data Definition Language &#xff09;数据 定义语言, 用来定义数据库对象(数…

手机怎么压缩图片?通过三种压缩操作

手机怎么压缩图片&#xff1f;在智能手机日益普及的今天&#xff0c;拍照分享已成为日常生活的一部分。然而&#xff0c;高质量的照片往往占用较大的存储空间&#xff0c;且在网络上传输时速度较慢。那么&#xff0c;如何在手机上压缩图片呢&#xff1f;本文将介绍三种实用的手…

C/C++图形库Easyx的使用教学

绘制简单的图形窗口 学会创建图形化窗口 包含头文件 graphics.h包含已被淘汰的函数easyx.h包含最新的函数 两个函数就可以创建窗口 Initgraph&#xff08;&#xff09;函数的定义 图形窗口的创建 #include<graphics.h>int main() {initgraph(800, 600);while (1);…

90%国际3A游戏发行商的首选,一文揭秘语音驱动面部动画生成技术!

在科技迅猛发展的时代&#xff0c;AI 正以破竹之势重塑着我们的世界。从激烈的“百模大战”到应用层生态的“百花齐放”&#xff0c; AIGC 产业迎来了快速增长的爆发期。AIGC 引领的创新应用&#xff0c;正推动着包括动漫游戏产业在内的各行各业加速升级。随着 AIGC 技术的不断…

SVN安装详细教程

&#x1f4d6;SVN安装详细教程 ✅1. 下载✅2. 安装✅3. 使用 ✅1. 下载 官方地址&#xff1a;https://tortoisesvn.net/downloads.html 123云盘地址&#xff1a;https://www.123pan.com/s/4brbVv-rsoWA.html ✅2. 安装 双击TortoiseSVN-1.14.6.29673-x64-svn-1.14.3.msi安装…

Kafka消费者api编写教程

1.基本属性配置 输入new Properties().var 回车 //创建属性Properties properties new Properties();//连接集群properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"node1:9092,node2:9092");//反序列化properties.put(ConsumerConfig.KEY_DESERIALIZER_CL…

云端狂飙:Django项目部署与性能优化的极速之旅

Hello&#xff0c;我是阿佑&#xff0c;这次阿佑将手把手带你亲自踏上Django项目从单机到云端的全过程&#xff0c;以及如何通过Docker实现项目的无缝迁移和扩展。不仅详细介绍了Docker的基本概念和操作&#xff0c;还深入探讨Docker Compose、Swarm和Kubernetes等高级工具的使…

车辆路径规划之Dubins曲线与RS曲线简述

描述 Dubins和RS曲线都是路径规划的经典算法&#xff0c;其中车辆运动学利用RS曲线居多&#xff0c;因此简单介绍Dubins并引出RS曲线。 花了点时间看了二者的论文&#xff0c;并阅读了一个开源的代码。 Dubins曲线 Dubins曲线是在满足曲率约束和规定的始端和末端的切线&#…

【python】IndexError: Replacement index 1 out of range for positional args tuple

成功解决“IndexError: Replacement index 1 out of range for positional args tuple”错误的全面指南 一、引言 在Python编程中&#xff0c;IndexError: Replacement index 1 out of range for positional args tuple这个错误通常发生在使用str.format()方法或者f-string&am…

网络编程 —— Http使用httpClient实现页面爬虫

先去找类型的a标签 取出图片所在网址 取出https://desk.3gbizhi.com/deskMV/438.html 搭建Form界面 Http类 public static HttpClient Client { get; } static Http() {HttpClientHandler handler new HttpClientHandler();//处理消息对象//ServerCertificateCustomValidat…

NeuralForecast TokenEmbedding 一维卷积 (Conv1d) 与矩阵乘法

NeuralForecast TokenEmbedding 一维卷积 (Conv1d) 与矩阵乘法 flyfish TokenEmbedding中使用了一维卷积 (Conv1d) TokenEmbedding 源码分析 在源码的基础上增加调用示例 下面会分析这段代码 import torch import torch.nn as nn class TokenEmbedding(nn.Module):def __i…

刷机 iPhone 进入恢复模式

文章目录 第 1 步&#xff1a;确保你有一台电脑&#xff08;Mac 或 PC&#xff09;第 2 步&#xff1a;将 iPhone 关机第 3 步&#xff1a;将 iPhone 置于恢复模式第 4 步&#xff1a;使用 Mac 或 PC 恢复 iPhone需要更多协助&#xff1f; 本文转载自&#xff1a;如果你忘记了 …

手写mybatis-预编译sql语句

sql表 mybatis数据库中的gxa_user表 /*Navicat Premium Data TransferSource Server : rootSource Server Type : MySQLSource Server Version : 80028Source Host : localhost:3306Source Schema : mybatisTarget Server Type : MySQLTarget…

图解DSPy:Prompt的时代终结者?!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

Windows Linux下查看静态库,动态库各种命令的总结

Windows环境下查看库文件 静态库(.lib) 使用lib.exe查看库内容 命令示例:lib /list C.lib使用dumpbin.exe查看库的详细信息 命令示例:dumpbin /headers C.lib动态链接库(.dll) 使用dumpbin.exe查看DLL的导出信息 命令示例:dumpbin /exports B.dll

选择富唯智能的可重构装配系统,就是选择了一个可靠的合作伙伴

在数字化、智能化的浪潮中&#xff0c;制造业正迎来一场前所未有的变革。而在这场变革中&#xff0c;富唯智能凭借其卓越的技术实力和创新能力&#xff0c;成为引领行业发展的领军企业。选择富唯智能的可重构装配系统&#xff0c;就是选择了一个可靠的合作伙伴&#xff0c;共同…

注册用户超6亿,哈啰发布年度可持续发展暨ESG报告

6月5日&#xff0c;哈啰发布《2023年度可持续发展暨ESG报告》&#xff0c;深入全面地展示2023年哈啰在可持续发展领域的举措和阶段性成果。 报告显示&#xff0c;哈啰始终遵循健康可持续的商业模式&#xff0c;以科技创新推动出行进化&#xff0c;在促进行业发展、环境友好、社…

Spring Boot 应用打 WAR 包后无法注册到 Nacos怎么办

你好&#xff0c;我是柳岸花开。 在微服务架构中&#xff0c;服务注册与发现是至关重要的一环。Nacos 作为阿里巴巴开源的注册中心&#xff0c;能够很好地满足这一需求。然而&#xff0c;在将 Spring Boot 应用打包成 WAR 部署到外部服务器时&#xff0c;可能会遇到服务无法注册…

【C++奇妙冒险】日期类Date的实现

文章目录 前言日期类Date的接口设计构造函数和打印函数获取日期并判断日期是否合法日期类的大小比较关系<运算符重载 判断小于运算符重载 判断相等<运算符重载 判断小于等于>运算符重载 判断大于> 运算符重载 判断大于等于! 运算符重载 不等于 日期类计算日期天数日…

WordPress 插件推荐:菜单缓存插件——Menu Caching

今天在缙哥哥博客上发现了一个 WordPress 速度优化插件的优化感觉很不错&#xff0c;明月自己装上也体验了一番&#xff0c; WordPress 菜单的载入速度无论是 PC 端和移动端都非常不错&#xff0c;并且这个叫 Menu Caching 的菜单缓存插件还完美的兼容 WPRocket&#xff0c;W3 …