C++虚基类、虚函数、虚析构函数、纯虚函数

2023-11-07

什么时多态

多态,即多种形态,是一种“泛型技术”,它企图使用不变的模板代码来实现可变的算法。在C++中,多态分为两种:

  1. 静态多态,就是说在编译时就能确定函数地址,通过复用函数名实现:如函数重载、运算符重载。
  2. 动态多态,就是能够在运行时确定函数地址,通过派生类和虚函数一起在运行时实现。

它们两者的区别就在于函数地址绑定的时间不同。函数重载和运算符载比较好理解。我们接下来主要了解派生类与虚函数一起是如何实现多态的。

虚函数

首先,我们要区分一下虚基类与虚函数,它们是不同的。基类是使用基类唯一化,虚函数则是能够调用派生类的函数,自身的函数实现被隐藏。

什么是虚基类

举个例子来说明一下什么是虚基类吧。

#include <iostream>

using namespace std;

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

class DerivedA:  public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB:  public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

class DerivedAll: public DerivedA,public DerivedB{
public:
    DerivedAll(){
        cout<< "Derived All"<<endl;
    }
};


int main()
{
   DerivedAll a;

    return 0;
}

打印结果:

Base
Derived A
Base
Derived B
Derived All

从上面打印出来的结果可以看出Base在内存中有两个副本。但实际上只需要一个Base副本就可以了。此时在继承类前加上关键字virtual,即:

class DerivedA:  virtual public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB: virtual public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

再次运行结果:

Base
Derived A
Derived B
Derived All

此时Base在内存只有一份了。因此虚基类就是让基类在内存中唯一化。

什么是虚函数

虚函数是指一个类中要重载的成员函数,当一个基类指针或引用指向一个继承类对象的时候,调用一个虚函数,实际调用的是派生类中的。否则,调用的就是基类中的。

#include <iostream>

using namespace std;

class Base {
public:
    void func(){
        cout<< "Base"<<endl;
    }
};

class DerivedA: public Base{
public:
    void func(){
        cout<< "Derived A"<<endl;
    }
};

int main()
{
   Base * pb = new DerivedA();
   pb->func();
    return 0;
}

打印结果:

Base

本来我们想通过基类指针调用派生类中的func方法,现在去调用了基类的。其实只需将基类中的要重载的方法前加上关键字virtual,使其成为虚函数,就可以实现用基类指针或引用来调用派生类中重载了的方法:

class Base {
public:
    virtual void func(){
        cout<< "Base"<<endl;
    }
};

再次运行的结果:

Derived A

编译器给每个对象和虚函数添加了一个隐形的成员:指向虚函数表的指针。虚函数表包含了虚函数的地址,由所有虚函数对象共用。
当派生类重新定义虚函数时,则将该函数的地址添加到虚函数表中。当一个基类指针指向一个派生类对象时,虚函数表指针指向派生类对象的虚函数表。
当调用虚函数时,由于派生类对象重写了派生类对应的虚函数表项,基类在调用时会调用派生类的虚函数,从而产生多态。

请记住,无论对象中定义了多少个虚函数,虚函数表指针只有一个,相应地,每个对象在内存中的大小要比没有虚函数大8B(64位机)或4B(32位机)。这是指针的大小。

派生类继承了基类的虚函数表指针,因此大小与基类一致。如果多重继承的另一个类也包括了虚函数的基类,那么隐藏成员就包括了两个虚函数表指针。例如:

#include <iostream>

using namespace std;

class Base {
public:
    void func1(){
        cout<< "Base func1"<<endl;
    }
    virtual void func2(){
        cout<< "Base func2"<<endl;
    }
    virtual void func3(){
        cout<< "Base func3"<<endl;
    }
};

class Derived: public Base{
public:
    virtual void func2(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * pb = new Derived();
   pb->func1();
   pb->func2();
   pb->func3();
    return 0;
}


Base func1
Derived func2
Base func3
  1. 首先,使用new关键字创建一个Derived对象,pb指针指向它。调用派生类构造函数会先调用基类的构造函数,然后再调用派生类的构造函数。
  2. 由于Derived继承了Base,所以Derived拥有Base的属性与方法,因此,对于pb->func1()时会调用Base的func1()函数。
  3. 由于Derived重写了func2()函数,在Derived对象中的虚函数表项中指向func2()函数的指针被修改为Derived::func2(),由于虚函数表指针为类对象的第一个字段,即基类指针指向派生类对象时,仍然会获取到派生类的虚函数表指针。因此pb->func2()时,程序会先通过派生类的虚函数表指针获取func2()的入口。
  4. 当进行pb->func3()时,由于派生类没有重写它,因此派生类的虚函数表里的func3()的入口仍然是Base的func3()函数。

虚析构函数

一个基类指针可以通过new产生一个派生类对象,如果delete关键字去删除这个指针时,仅仅会调用基类的析构函数,而派生类的空间没有被释放,这样会造成内存泄露。

为了防止内存泄露,当派生类中有指针成员变量时,才会使用到虚析构函数。虚析构函数使在删除指向派生类对象的基类指针时,可以通过调用派生类的析构函数来实现释放派生类所占内存,从而防止内存泄露。

#include <iostream>

using namespace std;

class Base {
public:
    virtual ~ Base(){
        cout<< "delete Base" << endl;
    }
    virtual void func(){
        cout<< "Base func"<<endl;
    }
};

class Derived: public Base{
public:
    ~ Derived(){
        cout << "delete Derived"<< endl;
    }
    void func(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * pb = new Derived();
   pb->func();
   delete pb;
    return 0;
}

Derived func2
delete Derived
delete Base

如果基类的析构函数不加virtual关键字,那它就是一个普通的析构函数。如果没有将基类的析构函数声明为虚析构函数,那么删除基类指针时,只会调用基类的析构函数,而不会调用派生类的析构函数。如果基类的析构函数声明为虚析构函数,那么删除基类指针时,会先调用派生类的析构函数,再调用基类的析构函数。

纯虚函数

纯虚函数相当于java、kotlin中的接口中的方法,只是声明,实现留给派生类来完成。纯虚函数的语法:

virtual 返回类型 函数名(参数列表)=0

纯虚函数与虚函数的区别是前者不需要实现,后者需要实现。在虚函数后加上“=0”,虚函数就变成纯虚函数了。

当类中有了纯虚函数,这个类就叫抽象类。由于抽象类中没有实现函数,所以它不能被实例化,其派生类必须重写纯虚函数,否则派生类也是一个抽象类。抽象类只是为了给其他类提供一个适合的基类。抽象类可以实现部分功能,把不能确定的部分声明为纯虚函数,留给其派生类来实现。纯虚函数一般会作为接口使用,用它约束代码,使用代码符合规范,利于开发大型项目。

class Base {
public:
    virtual void func() = 0;
};

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

C++虚基类、虚函数、虚析构函数、纯虚函数 的相关文章

随机推荐

  • 《编程珠玑》--读书笔记12章:取样问题

    第十二章 作者提出了一个问题 程序的输入包含两个整数m和n 其中m lt n 输出是0 n 1范围内的m个随机整数 不允许重复 有两种方法达到目的 1 思路 从r个剩余的整数中选出s个 以概率s r来选择下一个数 比如 m 2 n 5 选择
  • SaaS企业怎么做决策

    帆软会议室的墙上出现了 永不上市 利润分享 的字样 所有参观的SaaS创业者都被这八个字吸引 纷纷拿出手机拍照 帆软创始人薛宝表示 在帆软全国30多家办公室的墙上都贴着这8个大字 2006年 帆软用薛宝仅有的5000元积蓄创办了这家公司 从
  • linux进程及进程间同步通信机制

    进程 一 进程的存储器安排 这是 UNIX环境高级编程 中介绍的典型存储器安排 但现实也不一定非要如此 对于c c 来说 数据的存储方式还是认为3种 堆 栈 全局数据区 包括全局数据 静态数据 常量 二 僵尸进程和孤儿进程 进程在终止前向父
  • shiro基础

    shiro基础 shiro中有三大核心架构 subjects security manager realms 分别代表 使用shiro的主体 核心安全管理器和数据和安全管理的连接 使用shiro首先需要引入shiro需要的依赖 可以通过ma
  • 前端如何进行用户权限管理

    1 问题 假如在做一个管理系统 面向老师学生的 学生提交申请 老师负责审核 或者还需要添加其他角色 功能权限都不同 现在的问题是 每种角色登录看到的界面应该都是不一样的 那这个页面的区分如何实现呢 2 要不要给老师和学生各自设计一套页面 这
  • [论文阅读]《Database Maanagement Systems》-第九章

    第九章 TREE STRUCTURED INDEXING 树结构索引 P271 301 gracefully 优雅的 适当的 because it adjusts well to changes 因为它能很好地适应变化 page bound
  • 2013年11月21日星期四(地图)

    初始 DDraw BOB bob BOB textures tool mytool char buffer 80 BITMAP FILE bitmap8bit int world x 0 current position of viewin
  • 【vue2对接萤石云接口,实现摄像头直播页面的调取显示】

    vue2对接萤石云接口 实现摄像头直播页面的调取显示 使用ezuikit js插件 accessToken url 附上最新代码和demo 公司前端vue项目需要实现萤石云接口对接 买好萤石摄像头 注册萤石云账号并将设备添加到萤石云平台 开
  • 端口被占用怎么办?关闭占用端口的进程

    当你发现某个端口被占用时 但不知道是哪一个进程占用了端口 需要关闭占用该端口的进程 1 启动系统命令行 windows系统 win r 2 输入命令 netstat ano 可查看所有端口的使用情况 netstat aon findstr
  • Buck电路

    如果我们希望得到一个20V的直流电压源 我们能想到什么办法 很简单 从220V市电开始 通过变压器降压得到有效值为20V的电压 通过整流桥整流得到不稳定的直流电 接着用一个大的电解电容滤波 就可以得到20V的电压 那么如果我们现在希望得到一
  • 微信python小课_微信上其实还有很多你不知道的事,Python微信平台开发编写实录...

    本文主要讲述如何利用Python开发微信公众平台 说明 如果你是Python小白 爬虫小白 觉得本节课程的代码晦涩难懂 没关系 不需要懂 按照步骤一步步操作就可以了 这一节我们主要是搭框架 几乎没有真正的爬虫代码 这些代码和操作 你可能一生
  • POI生成excel表格——简单例子

    导入包 poi 2 5 1 jar User类 提供数据用 package com xk poi import java util ArrayList import java util List public class User priv
  • Linux 关于读者与写者同步互斥问题的解析

    在操作系统课程中 读者与写者问题 或是称作生产者与消费者问题 一直是同步与互斥问题的经典例子 在大学的操作系统课程中也是作为经典例子进行讲解 今天我们就来解析一下读者与写者的互斥同步关系 首先要做的是先了解一下问题的内容 首先需要肯定的是先
  • pycharm的使用技巧与遇到的问题

    文章目录 pycharm的console控制台无法使用 python运行时报错can t find main module in xxx 的解决办法 设置用ctrl 鼠标滚轮调整代码字体大小 console中无法导入常用标准库 刚安装的py
  • 【C++基础】公有继承,保护继承,私有继承

    目录 公有继承 public 保护继承 protected 私有继承 private 公有继承 保护继承 私有继承小结 公有继承 public 公有继承 对父类的公有成员和保护成员的访问属性不变 子类 派生类 的新增成员可以访问基类的公有成
  • Python中的UI设计程序Designer配置

    1 确认安装了Designer 我的基本开发环境是由安装Anaconda后一体化配置的 因此随之也安装了Designer应用程序 具体到我的电脑而言 安装后的路径是 E Python Anaconda3 Scripts designer e
  • 日志正则表达式模板

    结果 logger error Logger e getMessage n tat this getClass getName Thread currentThread getStackTrace 1 getMethodName Threa
  • 2021最新 阿里云上云--从自建服务器到选择阿里云

    上云前序 我们公司因为业务需求 需要来服务器托管微信公众号平台 之前我们先是自建服务器 然后就是使用IDC机房托管服务器 后来因为种种原因 最后转到了阿里云上云 很多同学会有疑问 为什么已经有了自建服务器 还需要去IDC机房托管 最后又去上
  • JS窗口问题处理:使弹出窗口保持前端显示的几种方法,及window窗体对象open()和showModalDialog()用法...

    用window open打开一个窗口 怎么使它一直在父窗口的上面 除非点击自己设置的关闭按钮 我试了一下 nblur self focus 好像不管用 可使用 opened window open demo html demo left 8
  • C++虚基类、虚函数、虚析构函数、纯虚函数

    什么时多态 多态 即多种形态 是一种 泛型技术 它企图使用不变的模板代码来实现可变的算法 在C 中 多态分为两种 静态多态 就是说在编译时就能确定函数地址 通过复用函数名实现 如函数重载 运算符重载 动态多态 就是能够在运行时确定函数地址