C++类基础(十五)

news2024/11/16 12:25:48

类的继承——虚函数(二)
● 由虚函数所引入的动态绑定属于运行期行为,与编译期行为有所区别

虚函数与继承紧密相关

– 虚函数的缺省实参只会考虑静态类型

struct Base
{
    virtual void fun(int x = 3)
    {
        std::cout << "virtual void fun(int x = 3): " << x << std::endl;
    }
};
struct Derive : Base
{
    void fun(int x = 4) override
    {
        std::cout << "void fun(int x = 4) override: " << x << std::endl;
    }
};
void ptrProc(Base* b)
{
    b->fun(); //编译期翻译为b->fun(3)
}
void refProc(Base& b)
{
    b.fun(); //编译期翻译为b.fun(3)
}
int main()
{
    Derive d;
    ptrProc(&d); //等价于ptrProc(static_cast<Base&>(d)),虚函数的缺省实参只会考虑静态类型,所以输出3
    refProc(d); //等价于refProc(static_cast<Base&>(d)),同上
    return 0;
}

在这里插入图片描述

– 虚函数的调用成本高于非虚函数

Java里面所有函数都是虚函数实现,对用户很友好

● final 关键字

一方面表示某个类不会被派生或者某个虚函数不会被修改,给编译器最大的余地优化。
struct Derive final: Base, C++11

struct Base
{
    virtual void fun(int x = 3)
    {
        std::cout << "virtual void Base::fun(int x = 3): " << x << std::endl;
    }
};
struct Derive : Base
{
    void fun(int x = 4) override final //final关键字
    {
        std::cout << "void Derive::fun(int x = 4) override: " << x << std::endl;
    }
};
struct Derive2 : Derive
{
    //void fun(int x = 5) //不能再重写虚函数,Error: Declaration of 'fun' overrides a 'final' function
    void func(int x = 5)
    {
        std::cout << "void Derive2::func(int x = 5) override: " << x << std::endl;
    }
};
struct Derive3 : Derive2
{
    void func(int x = 6)
    {
        std::cout << "void Derive3::func(int x = 6) override: " << x << std::endl;
    }
};
void ptrProc(Base* b)
{
    b->fun();
}
void refProc(Base& b)
{
    b.fun();
}
int main()
{
    Derive d;
    ptrProc(&d);
    refProc(d);
    std::cout << '\n';

    Derive2 d2;
    ptrProc(&d2);
    refProc(d2);
    d2.func();
    std::cout << '\n';

    Derive3 d3;
    ptrProc(&d3);
    refProc(d3);
    d3.func();
    return 0;
}

在这里插入图片描述
– 为什么要使用指针(或引用)引入动态绑定

void Proc(Base b)
//void Proc(Base b)开辟了另外一块内存,构造了一个Base对象,使用Base类内的函数版本,构造在编译期执行,因此只能通过指针或引用引入动态绑定
//引用的底层实现是指针,因此讨论指针void Proc(Base* b): 只是构造了一个指针,指向原始的对象,调用函数时,
//根据指针找到原始对象,再根据原始对象找到vtable,再根据vtable决定调用的函数,
//在这个过程中,没有Derive到Base的转换,因此才能调用相应的虚函数版本
{
    b.fun();
}

– 在构造函数中调用虚函数要小心

struct Base
{
    Base()
    {
        fun(); //一定不会调用派生类的重写函数
        std::cout << "Base()\n";
    }
    virtual void fun()
    {
        std::cout << "virtual void Base::fun()" << std::endl;
    }
};
struct Derive final: Base //C++11
{
    Derive()
        : Base()
    {
        fun();
        std::cout << "Derive()\n";
    }
    void fun() override
    {
        std::cout << "void Derive::fun()" << std::endl;
    }
};
int main()
{
    Derive d; //执行到Derive()时d还没有构造好(先构造Base),进入Base()内部到第一行fun()的时候,
    //Base已经(缺省)初始化好了,即vtable已构造好,vtable里面的所有虚函数都会指向Base内的虚函数
    //执行到Derive() : Base() { fun();时Derive已经构造完成,vtable里面的基类虚函数版本被替换成派生类中重写的版本,因此会打印重写版本中的内容
    return 0;
}

在这里插入图片描述

– 派生类的析构函数会隐式调用基类的析构函数

struct Base
{
    ~Base()
    {
        std::cout << "~Base()\n";
    }
};
struct Derive final: Base
{
    ~Derive()
    {
        std::cout << "~Derive()\n";
    }
};
int main()
{
    Derive obj; //编译器已知Derive的析构函数地址,Base的析构函数地址和Derive继承自Base,所以销毁时先调用~Derive()然后~Base()
    Derive* d = new Derive(); //动态申请一个Derive对象
    Base* b = d; //创建一个Base类型的指针指向Derive对象
    delete b; //释放内存,调用基类的析构函数是在编译期决定的,所以只调用~Base()
    //C++标准规定该程序的行为未定义(系统会表现出任何意义的行为)。大概率的行为会是只调用基类的析构函数
    return 0;
}

在这里插入图片描述

#include<memory>
struct Base
{
    ~Base()
    {
        std::cout << "~Base()\n";
    }
};
struct Derive final: Base
{
    ~Derive()
    {
        std::cout << "~Derive()\n";
    }
};
int main()
{
    std::shared_ptr<Base> sptr(new Derive());
    std::unique_ptr<Base> uptr(new Derive());
    return 0;
}

– 通常来说要将基类的析构函数声明为 virtual 的

struct Base
{
    //virtual ~Base() = default; Since C++11,目的不是声明一个缺省析构函数,而是把它声明成虚函数,所有派生自基类的类都会隐式添加virtual关键字
    virtual ~Base() //将析构函数声明为virtual
    {
        std::cout << "~Base()\n";
    }
};
struct Derive final: Base
{
    ~Derive() //派生类析构函数会继承基类虚函数的virtual特性
    {
        std::cout << "~Derive()\n";
    }
};
int main()
{
    std::shared_ptr<Base> sptr(new Derive()); //C++规定当执行派生类的析构函数时发现它是虚函数,就会找到基类的析构函数并执行
    std::unique_ptr<Base> uptr(new Derive());
    return 0;
}

在这里插入图片描述

当用派生类类型的指针挂载派生类对象的时候,释放该指针,不需要将基类的析构函数声明成虚函数。
当用基类类型的指针挂载派生类对象时,释放该指针,一定要将基类的析构函数声明成虚函数。
大部分情况下会用基类类型的指针挂载(绑定)派生类对象,很少会用派生类类型的指针挂载派生类对象。

– 在派生类中修改虚函数的访问权限

struct Base
{
protected: //访问权限是编译期行为,访问权限限定符只会影响到会不会被外部访问,不会影响到vtable里面指向虚函数的位置
    virtual void fun()
    {
        std::cout << "virtual void Base::fun()\n";
    }
};
struct Derive final: Base
{
public:
    void fun() override //重写的概念是替换槽里的东西
    {
        std::cout << "void Derive::fun()\n";
    }
};
int main()
{
    Derive d; //静态类型,所以d.fun()只看void Derive::fun() override
    d.fun(); //能否通过编译是在编译期决定,能否通过编译的其中一个重要条件是该函数是否具有外部访问权限
    Base& b = d; //b的静态类型是Base&
    b.fun(); //编译期只看静态类型,所以只看virtual void Base::fun() Error: 'fun' is a protected member of 'Base'
    return 0;
}

参考
深蓝学院:C++基础与深度解析

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

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

相关文章

国产技术迎来突破,14nm芯片横空出世,低代码也有好消息

芯片&#xff0c;被称为工业时代的“粮食”&#xff0c;小到手机手环&#xff0c;大到飞机轮船&#xff0c;几乎各个行业都不离开芯片的支持&#xff0c;其重要性不言而喻。而我国在这一领域一直较为薄弱。 一、“芯片之路坎坷” 由于国内半导体芯片市场底子薄弱、没有主动权…

NetApp AFF A 系列全闪存存储阵列

NetApp AFF A 系列全闪存阵列是一款智能、至强、至信的解决方案&#xff0c;它可利用现代云技术为您的 Data Fabric 提供所需的速度、效率和安全性。 是时候实现数据现代化了 进行任何 IT 转型的基础性第一步是利用高性能全闪存存储打造现代化基础架构&#xff0c;提高关键业务…

【C++之容器适配器】反向迭代器的实现

目录前言一、反向迭代器的实现1. 底层2. 成员函数1. 构造函数2. operator*()3. operator->()4. 前置5. 后置6. 前置--7. 后置--8. operator!()9. operator()二、vector反向迭代器的实现1. vector的正向迭代器2. vector反向迭代器的实现3. 测试vector的反向迭代器三、list反向…

git提交

文章目录关于数据库&#xff1a;桌面/vue-admin/vue_shop_api 的 git 输入 打开 phpStudy ->mySQL管理器 导入文件同时输入密码&#xff0c;和文件名 node app.js 错误区&#xff1a; $ git branch // git branch 查看分支 只有一个main分支不见master解决&#xff1a; gi…

PyQt5保姆级入门教程——从安装到使用

目录 Part1&#xff1a;安装PyQt5 Part 2&#xff1a;PyCharm配置PyQt5 Part 3&#xff1a;PyQt5设计界面介绍 Part 4&#xff1a;PyQt5设计UI 今天看了多个大佬的教程&#xff0c;总算是把PyQt5开发弄好了&#xff0c;每个部分都要看几个人的十分不方便&#xff0c;我十分…

YOLOv3简介

YOLOv3 预测部分 Darknet-53 YOLOv3的主干提取网络为Darknet-53&#xff0c;相比于YOLOv2时期的Darknet-19&#xff0c;其加深了网络层数且引入了Residual残差结构。其通过不断的1X1卷积和3X3卷积以及残差边的叠加&#xff0c;大幅度的加深了网络。残差网络的特点是容易优化&a…

【Unity VR开发】结合VRTK4.0:将浮点数从交互器传递到可交互对象

语录&#xff1a; 愿你熬得过万丈孤独&#xff0c;藏得下星辰大海。 前言&#xff1a; 默认情况下&#xff0c;交互器只能将单个布尔操作传递给可交互对象&#xff0c;后者控制可交互对象上的抓取操作。在其他时候&#xff0c;交互器中的其他操作可能希望传递给可交互对象&…

leaflet 设置marker,并可以任意拖动每一个marker(071)

第071个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中通过L.marker来添加marker,通过设置其属性,可以让marker在地图上任意的拖动。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共76行)相关API参…

实体店店铺管理软件怎么挑?看了排名就知道!

很多实体店店主在选择店铺管理软件时,不知道怎么选择,其实这个不难。一般根据市场上的排名也选择就ok了&#xff0c;因为一款被大家都认证过好用的软件&#xff0c;怎么都比盲选的或者名不见经传的软件好。选择一款适合的实体店店铺管理软件可以省很多事。而截止现在管理软件排…

linux将新加磁盘绑挂载到指定目录

查看当前挂载情况df -l此时可以看到sda和sdb两块磁盘已经被挂载&#xff0c;但实际上还有更多块磁盘未被挂载&#xff08;磁盘名称sda&#xff0c;结尾字母安顺递增&#xff09;查看一安装的所有磁盘fdisk -l此时我们可以看到还有很多未进行分区磁盘为磁盘添加分区fdisk /dev/s…

【另辟蹊径】Table 单元格内容过多之省略展示方案,设置Element table的 showOverflowTooltip 属性无效后的替代方案

一、问题背景 设置了element table的组件 <el-table-column>属性showOverflowTooltip无效&#xff0c;如图所示。 PS&#xff1a;注意不是不起作用&#xff0c;是有作用但是内容过多展示占据了整个界面&#xff0c;影响美观和用户体验。 有的博主解决方法是全局样式文件…

一眼万年的 Keychron 无线机械键盘

一眼万年的 Keychron 无线机械键盘 一款好的键盘对于程序员或者喜欢码字的人来说是非常重要的&#xff0c;而最近博主入手了自己的第一款机械键盘——Keychron 无线机械键盘。 机械键盘特点 有独立轴体&#xff0c;通过两个簧接触&#xff0c;来触发信号&#xff0c;价格相对贵…

大文件上传如何做断点续传?

一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用户体验 服务器处理数据的能力请求超时 网络波动 上传时间会变长&#xff0c;高…

缓存穿透-总结

目录 缓存穿透-总结 出现场景&#xff1a; 解决方法&#xff1a; 方法1.缓存空对象&#xff1a; 方法2.加一个布隆过滤器&#xff1a; 总结&#xff1a; 缓存穿透-总结 出现场景&#xff1a; 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓…

光量子领域新突破:有望打造芯片工厂!

将2D材料与氮化硅谐振器混合集成&#xff0c;使一系列单光子源与硅基光子按需精准结合。&#xff08;图片来源&#xff1a;网络&#xff09;量子光子学的著名专家、电气和计算机工程助理教授Galan Moody的实验室成功创造了一种在芯片上产生单光子的新方法。量子具有叠加态的特性…

飞桨特色产业级模型库,助力AI开发与落地更简单!

飞桨在长期的产业实践中发现&#xff0c;开发者使用开源模型项目落地普遍会遇到三大难题&#xff1a; 算法和模型繁多&#xff0c;做模型选择是个难题&#xff1b; 模型效果不错&#xff0c;但产业落地时容易遇到资源限制和部署的问题&#xff1b; 面对新场景无从下手&#x…

minio public桶禁止在直接访问桶位置时列出所有文件url

minio的public桶因为没有限制&#xff0c;所以在直接访问到桶地址的时候会列出桶内所有文件的url&#xff0c;这样很不安全&#xff0c;如何禁止这个功能&#xff0c;可以使用三种方法 1、如果是新版的可以直接设置桶的Access Policy为自定义就好 编辑custom的Policy&#xff…

五种情况下企业需要引进低代码开发平台

随着低代码开发平台的热度在上升&#xff0c;企业中也开始流行一种新的应用交付方式&#xff1a;业务部门基于低代码开发平台将所需要的功能&#xff08;或简单的可用版本&#xff09;自行搭建出来&#xff0c;当遇到较为复杂的需求时&#xff0c;则向IT部门请求支援。业务与IT…

【MFC】模拟采集系统——数据绘制(19)

完成界面设计后&#xff0c;数据绘制也可以按照对MFC类派生来完成&#xff0c;值得注意的是这里的数据绘制仅仅是通过随机产生的数据来显示&#xff0c;并且显示的方法也有很多。 数据绘制 在主对话框中添加两个 Picture Control 位置大小任意&#xff0c;可以设置一下外观&a…

Python3 数据结构实例及演示

本章节主要结合前面所学的知识点来介绍Python数据结构。 列表 Python中列表是可变的&#xff0c;这是它区别于字符串和元组的最重要的特点&#xff0c;1句话概括即&#xff1a;列表可以修改&#xff0c;而字符串和元组不能。 以下是 Python 中列表的方法&#xff1a; 下面示…