虚函数与纯虚函数定义及区别,抽象类

2023-11-19

目录

虚函数和纯虚函数的区别:

二、虚函数的实现机制

三、构造函数、析构函数是否需要定义成虚函数

四、构造函数和析构函数中能否调用虚函数


虚函数与纯虚函数定义

一、定义
虚函数:被 virtual 关键字修饰的成员函数。

纯虚函数: 在类中声明虚函数时加上 =0;

抽象类:含有纯虚函数的类(只要含有纯虚函数这个类就是抽象类),类中只有接口,没有具体的实现方法。
继承纯虚函数的派生类,如果没有完全实现基类纯虚函数,依然是抽象类,不能实例化对象。

虚函数和纯虚函数的区别:

虚函数和纯虚函数可以出现在同一个类中,该类称为抽象基类。(含有纯虚函数的类称为抽象基类)
1、使用方式不同:虚函数可以直接使用,纯虚函数必须在派生类中实现后才能使用;
2定义形式不同:虚函数在定义时在普通函数的基础上加上 virtual 关键字,纯虚函数定义时除了加上virtual 关键字还需要加上 =0;
3、虚函数必须实现,否则编译器会报错;
对于实现纯虚函数的派生类,该纯虚函数在派生类中被称为虚函数,虚函数和纯虚函数都可以在派生类中重写;
析构函数最好定义为虚函数,特别是对于含有继承关系的类(析构函数被定义为虚函数之后,在使用指针引用时可以动态绑定,实现运行时的多态,保证使用基类指针就能够调用适当的虚构函数针对不同的对象进行清理工作。);析构函数可以定义为纯虚函数,此时,其所在的类为抽象基类,不能创建实例化对象

说明:

抽象类对象不能作为函数的参数,不能创建对象,不能作为函数返回类型;
可以声明抽象类指针,可以声明抽象类的引用;
子类必须继承父类的纯虚函数,并全部实现后,才能创建子类的对象。

二、虚函数的实现机制

虚函数通过虚函数表来实现。

        虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表。

        虚函数表解决了基类和派生类的继承问题和类中成员函数的覆盖问题,当用基类的指针来操作一个派生类的时候,这张虚函数表就指明了实际应该调用的函数。

说明:

虚函数表存放的内容:类的虚函数的地址
虚函数表建立的时间:编译阶段,即程序的编译过程中会将虚函数的地址放在虚函数表中。
虚表指针保存的位置:虚表指针存放在对象的内存空间中最前面的位置,这是为了保证正确取到虚函数的偏移量。
虚函数表和类绑定,虚表指针和对象绑定。即类的不同的对象的虚函数表是一样的,但是每个对象都有自己的虚表指针,来指向类的虚函数表。

编译器处理虚函数表:

编译器将虚函数表的指针放在类的实例对象的内存空间中,该对象调用该类的虚函数时,通过指针找到虚函数表,根据虚函数表中存放的虚函数的地址找到对应的虚函数。
如果派生类没有重新定义基类的虚函数 A,则派生类的虚函数表中保存的是基类的虚函数 A 的地址,也就是说基类和派生类的虚函数 A 的地址是一样的。
如果派生类重写了基类的某个虚函数 B,则派生的虚函数表中保存的是重写后的虚函数 B 的地址,也就是说虚函数 B 有两个版本,分别存放在基类和派生类的虚函数表中。
如果派生类重新定义了新的虚函数 C,派生类的虚函数表保存新的虚函数 C 的地址。
单继承无虚函数覆盖:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void B_fun1() { cout << "Base::B_fun1()" << endl; }
    virtual void B_fun2() { cout << "Base::B_fun2()" << endl; }
    virtual void B_fun3() { cout << "Base::B_fun3()" << endl; }
};

class Derive : public Base
{
public:
    virtual void D_fun1() { cout << "Derive::D_fun1()" << endl; }
    virtual void D_fun2() { cout << "Derive::D_fun2()" << endl; }
    virtual void D_fun3() { cout << "Derive::D_fun3()" << endl; }
};
int main()
{
    Base *p = new Derive();
    p->B_fun1(); // Base::B_fun1()
    return 0;
}


基类的虚函数表:

派生类的虚函数表:

多继承有虚函数覆盖:

#include <iostream>
using namespace std;

class Base1
{
public:
    virtual void fun1() { cout << "Base1::fun1()" << endl; }
    virtual void B1_fun2() { cout << "Base1::B1_fun2()" << endl; }
    virtual void B1_fun3() { cout << "Base1::B1_fun3()" << endl; }
};
class Base2
{
public:
    virtual void fun1() { cout << "Base2::fun1()" << endl; }
    virtual void B2_fun2() { cout << "Base2::B2_fun2()" << endl; }
    virtual void B2_fun3() { cout << "Base2::B2_fun3()" << endl; }
};
class Base3
{
public:
    virtual void fun1() { cout << "Base3::fun1()" << endl; }
    virtual void B3_fun2() { cout << "Base3::B3_fun2()" << endl; }
    virtual void B3_fun3() { cout << "Base3::B3_fun3()" << endl; }
};

class Derive : public Base1, public Base2, public Base3
{
public:
    virtual void fun1() { cout << "Derive::fun1()" << endl; }
    virtual void D_fun2() { cout << "Derive::D_fun2()" << endl; }
    virtual void D_fun3() { cout << "Derive::D_fun3()" << endl; }
};

int main(){
    Base1 *p1 = new Derive();
    Base2 *p2 = new Derive();
    Base3 *p3 = new Derive();
    p1->fun1(); // Derive::fun1()
    p2->fun1(); // Derive::fun1()
    p3->fun1(); // Derive::fun1()
    return 0;
}


派生类的虚函数表:

三、构造函数、析构函数是否需要定义成虚函数

构造函数不定义为虚函数
构造函数是在实例化对象的时候进行调用,如果此时将构造函数定义成虚函数,需要通过访问该对象所在的内存空间才能进行虚函数的调用(因为需要通过指向虚函数表的指针调用虚函数表,虽然虚函数表在编译时就有了,但是没有虚函数的指针,虚函数的指针只有在创建了对象才有),但是此时该对象还未创建,便无法进行虚函数的调用。所以构造函数不能定义成虚函数。

析构函数定义成虚函数
析构函数定义成虚函数是为了防止内存泄漏,因为当基类的指针或者引用指向或绑定到派生类的对象时,如果未将基类的析构函数定义成虚函数,会调用基类的析构函数,那么只能将基类的成员所占的空间释放掉,派生类中特有的就会无法释放内存空间导致内存泄漏。

四、构造函数和析构函数中能否调用虚函数

        在构造函数不要调用虚函数。

        在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。
        在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
补充说明:
1.如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。
2. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了
3. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

虚函数与纯虚函数定义及区别,抽象类 的相关文章

随机推荐

  • 移植tslib时ts_setup: No such file or directory、ts_open: No such file or director

    作者 Jack G 时间 2021 04 20 版本 上次修改时间 批注 ts test mt ts setup No such file or directory ts test ts open No such file or direc
  • [极客大挑战 2019] Knife1

    这道题原本很简单的可以用蚁剑连接 但是我的蚁剑多多少少有点问题 所以我用hackbar解题 提供另外一种思路 如果你和我一样的话也可以用hackbar 打开题目 发现直接给了我们连接密码 打开hackbar 先测试一下能否连接上 Syc p
  • 节点主动可信监控机制

    节点主动监控机制一般是通过调用在操作系统 虚拟机监视器 VMM 底层函数和中间件中的钩子函数来实现对上层行为的监控 监控过程过程可抽象为可信度量 可信决策 可信控制 同时 对系统中已有的安全机制 可信软件也可以通过策略输出和审计接入将它们纳
  • vue3变化+vue3项目的创建

    VUE3新特性 createApp 在 Vue 3 中 改变全局 Vue 行为的 API 现在被移动到了由新的createApp方法所创建的应用实例上 vue3 0中使用createApp 来创建vue实例 import createApp
  • 1-PointNetGPD论文阅读

    资源相关 1 项目地址 https lianghongzhuo github io PointNetGPD 2 源码地址 https github com lianghongzhuo PointNetGPD 3 论文地址 https arx
  • gdb调试心得体会

    gdb调试心得体会 首先进入gdb 调试二进制程序 gdb msgsvr dev 然后 运行 run 然后coredump了 输入bt查看调用栈 bt 然后查看函数栈 f 进入到指定的函数 然后查看具体行数 l number 然后break
  • Conditional Prompt Learning for Vision-Language Models

    本文是对CoOp方法提出的改进 CoOp由论文Learning to Prompt for Vision Language Models提出 CoOp针对CLIP模型做了改进 将人工设计的提示修改为了可学习的参数 具体来说就是 CoOp不再
  • Visual Studio 2022 CMake C++ Hello World

    C 自学精简教程 目录 必读 Visual Studio 2022 安装 什么是CMake CMake是跨平台的C C 工程构建工具 我们知道 在Windows上用Visual Studio开发C C 代码 工程文件是用 vcxproj文件
  • chatgpt赋能Python-python_3__3

    Python 3 3 深入探讨Python中的相等运算符 在Python中 我们经常需要比较两个值是否相等 而Python的相等运算符 是用来判断两个值是否相等 在这篇文章中 我们将深入探讨Python中的 运算符 两个等号的作用 在Pyt
  • Maven 项目打包源文件 *-sources.jar

    在 pom xml 配置文件中添加以下插件
  • 瑞数信息联合中国信通院发布《云上WAAP发展洞察报告(2023)》

    8月25日 由中国信息通信研究院 以下简称 中国信通院 和中国通信标准化协会联合主办的 2023云和软件安全大会 在北京召开 会上 瑞数信息与中国信通院云计算与大数据研究所联合撰写的 云上WAAP发展洞察报告 2023 以下简称 报告 正式
  • 区块链能够解决价值对等问题吗?

    如果说互联网让信息透明和平等 降低或者使得获取信息成本为零 那么区块链则是让价值更公平 原因在于区块链技术的去中心化与分布式数据存储 一般来说 商业进化需经历三个阶段 由PC互联网 移动互联网所控制的信息互联网 称为第一阶段 由物联网 人工
  • Lottie动画概述,文末有彩蛋

    原生的动画效果有时候写起来会非常的复杂 要考虑到很多兼容和效果 Lottie动画专门为了解决这种烦恼而产生的 注 不仅是Lottie可以做到 另外一种库也可以做到将动画分成一帧一帧展示 它就是 android gif drawable 库
  • Dynamics 365 Business Process Flow -- 让你不再惧怕复杂的业务流程!

    Business Process Flow 并不是新功能 它最初是在Dynamics CRM 2013中被发布的 刚推出的时候 用户体验和开发体验并不是非常的完善 随着版本的不断迭代 新功能也不断的被增加 特别是在最近发布的Dynamics
  • AIX系统解压tar.gz文件

    gunzip c apache tar gz tar xvf
  • C++ 如何将一个大的整数 拆分0到9单个数字

    如何将一个大的整数拆分成单个整数 第一种解决方案 第二种解决方案 分享思路 希望能帮到你 第一种解决方案 纯算法的方式 完整数 int value 123456 拆分后的个位数 int sub 拆分 while value 得到当前整数 尾
  • ORB-SLAM3---imu相关

    1 IMU简介及参数说明 2 预积分推导 纸老虎 1 反对称矩阵 2 反对称矩阵反过来 3 旋转向量到旋转矩阵 上面是积分 下面是预积分 3 噪声分离
  • 【超详细!】Snort在Win-7下的安装配置及可视化

    做学校实验做到秃头的产物 记录一下我一边考试一边实验的疯狂期末周 前排提示本人是个看到修改一大堆配置就头疼的菜狗 所以这篇教程尽可能减少了修改配置 包含了本人遇到的坑 解决方案 我尽力了朋友们 一 前期资源准备 1 win 7环境虚拟机 这
  • JavaScript常见调试方法

    编辑导语 javascript调试方法 常见使用alert和console来定位出错和输出的结果是否是想要的 在chrome中 还可以使用断点来看运行的情况等 本文介绍了比较全面的调试方法 你知道console table console
  • 虚函数与纯虚函数定义及区别,抽象类

    目录 虚函数和纯虚函数的区别 二 虚函数的实现机制 三 构造函数 析构函数是否需要定义成虚函数 四 构造函数和析构函数中能否调用虚函数 虚函数与纯虚函数定义 一 定义虚函数 被 virtual 关键字修饰的成员函数 纯虚函数 在类中声明虚函