抽象类相关

news2025/4/25 12:48:11

抽象类的定义

  1. 抽象类 是一种特殊的类,它不能被实例化,只能作为基类来派生出具体类。
  2. 抽象类至少包含一个纯虚函数 。纯虚函数是在函数原型前加上 = 0 的虚函数,表示该函数没有具体实现,必须由派生类来实现。

抽象类的作用

  1. 提供统一接口 :抽象类定义了一组接口规范,要求所有派生类都实现这些接口。这样可以确保不同类型的对象具有相同的调用方式,使得代码更加通用和灵活。
  2. 避免重复代码 :通过在抽象类中定义通用的成员函数,派生类可以继承这些函数,从而避免重复实现相同的逻辑。
  3. 支持多态 :抽象类和纯虚函数是实现多态的关键。通过基类指针或引用,可以调用派生类的具体实现,使得程序在运行时能够根据对象的实际类型执行相应的操作。

抽象类的原理

  1. 纯虚函数的实现 :在C++中,纯虚函数是通过在编译时标记为未实现的函数来实现的。编译器会确保所有派生类都必须实现这些纯虚函数,否则派生类也会成为抽象类。
  2. 动态绑定 :当通过基类指针或引用来调用虚函数时,编译器会生成代码来动态决定调用哪个派生类的函数实现。这是通过虚函数表(vtable)和虚表指针(vpointer)来实现的。
  3. 虚函数表(vtable :每个类都有一个虚函数表,它是一个包含类中所有虚函数地址的数组。当创建一个派生类的对象时,其虚表指针会指向派生类的虚函数表。
  4. 虚表指针(vpointer :每个包含虚函数的类的对象都有一个隐藏的指针,称为虚表指针,它指向该类的虚函数表。当通过基类指针调用虚函数时,编译器会通过虚表指针查找并调用相应的函数实现。

示例代码

#include <iostream>
using namespace std;

class Abstract
{
public:
    virtual void display() = 0; // pure virtual function
    Abstract(){
        cout << "Constructor of Abstract class" << endl;
    }
    virtual ~Abstract()
    {
        cout << "Destructor of Abstract class" << endl;
    }
};

class Derived : public Abstract
{
public:
    void display()
    {
        cout << "Display function of Derived class" << endl;
    }
    Derived(){       
        cout << "Constructor of Derived class" << endl;
    }
    ~Derived()
    {
        cout << "Destructor of Derived class" << endl;
    }
};

void call(Abstract *obj)
{
    obj->display();
}

int main()
{
    Derived d;
    call(&d);

    return 0;
}

在这里插入图片描述

为什么构造函数不能被声明为虚函数?

在这里插入图片描述
构造函数是给对象分配内存和初始化成员变量的,编译器要提前知道调用哪个构造函数,才能准确地分配内存和初始化。
就好比造汽车,必须先确定用哪个模具(构造函数),才能准备好材料和工具(分配内存)并开始组装(初始化成员变量)。

构造函数不是用来实现运行时多态的,它是为了确保对象在创建时就处于一个有效的初始状态。虚函数机制用于支持运行时多态,是通过虚函数表(vtable)和虚表指针(vpointer)实现的。

C++中,创建派生类对象时,基类构造函数先执行,接着才是派生类构造函数。假若构造函数被设为虚函数,那在基类构造函数运行时,派生类构造函数还没执行,对象的动态类型尚未确定,编译器就无法知晓该调用哪个构造函数。

另外,虚函数表的初始化是在对象构造过程中完成的。在构造函数执行前,虚函数表指针(vpointer)尚未初始化。若构造函数是虚函数,调用时虚函数表指针未设置好,就无法确定具体调用哪个构造函数。所以,构造函数不能是虚函数。

正是因为构造函数的调用必须在编译时确定,而虚函数机制依赖于运行时的动态绑定,所以构造函数不能是虚函数。这样的设计确保了对象能够正确初始化,并且构造过程是可预测的。

虚函数表(vtable)的初始化主要发生在哪些阶段?

1. 编译阶段,为每个包含虚函数的类生成虚函数表

虚函数表是一个包含该类所有虚函数地址的数组。

编译器会根据类的定义和继承关系来构建这个表。

对于派生类,其虚函数表会包含基类的虚函数和派生类自己的虚函数,如果派生类重写了基类的虚函数,则虚函数表中将使用派生类的实现覆盖基类的实现。

2. 对象构造阶段,为每个创建的对象分配虚表指针(vpointer

虚表指针(vpointer)指向该对象对应的虚函数表。

这个指针的初始化发生在对象的构造函数执行之前。

具体来说,基类构造函数首先执行,在基类构造函数中,对象的虚表指针被初始化为基类的虚函数表。当派生类构造函数执行时,虚表指针会被更新为派生类的虚函数表。

3. 动态绑定阶段,通过虚表指针查找对应函数

在对象的生命周期中,虚表指针始终指向该对象对应的虚函数表。

当调用虚函数时,编译器会通过虚表指针查找并调用相应的函数。

如果对象的类型在运行时发生变化(例如,通过多态),虚表指针会指向不同的虚函数表,从而实现动态绑定。

4. 总结

虚函数表的初始化主要在编译阶段和对象构造阶段完成。

编译器在编译时为每个类生成虚函数表,在对象构造时为对象的虚表指针赋值。

虚函数表的初始化确保了在调用虚函数时能够正确地找到对应的函数实现,支持运行时的多态行为。

纯虚函数和虚函数的区别

在这里插入图片描述
纯虚函数 :在函数声明后面加上 = 0,表示该函数没有具体实现,必须由派生类来实现。包含纯虚函数的类是抽象类,不能被实例化。
虚函数 :普通的虚函数有具体实现,可以在基类中定义,派生类可以重写也可以不重写。如果派生类不重写,将继承基类的实现。

两者都用于实现运行时多态性,但纯虚函数强制派生类必须提供实现,而虚函数允许派生类选择是否重写。

包含纯虚函数的类是抽象类,不能实例化;而包含虚函数的类可以是具体类,可以实例化。

调用 call(&d) 时,对象的创建和销毁过程

在这里插入图片描述

1. 对象创建过程

当执行 Derived d; 时:

  1. 分配内存 :为 Derived 类对象 d 分配内存,包括基类 Abstract 的成员和派生类 Derived 的成员。
  2. 调用基类构造函数 :首先调用基类 Abstract 的构造函数 Abstract(),初始化基类的部分。
  3. 调用派生类构造函数 :然后调用派生类 Derived 的构造函数 Derived(),初始化派生类的部分。

执行顺序如下:

Constructor of Abstract class
Constructor of Derived class

2. 调用 call(&d)

当执行 call(&d); 时:

  1. 传递指针 :将 d 的地址传递给 call 函数,函数参数 obj 是一个指向 Abstract 类的指针。
  2. 动态绑定 :在 call 函数内部,通过 obj->display(); 调用 display() 函数。由于 display() 是纯虚函数,编译器会根据 obj 实际指向的对象类型(这里是 Derived 类),动态绑定到 Derived 类的 display() 实现。
  3. 执行派生类函数 :调用 Derived 类的 display() 函数,输出 Display function of Derived class

3. 对象销毁过程

main 函数结束时,对象 d 的生命周期结束,执行以下步骤:

  1. 调用派生类析构函数 :首先调用派生类 Derived 的析构函数 ~Derived()
  2. 调用基类析构函数 :然后调用基类 Abstract 的析构函数 ~Abstract()

执行顺序如下:

Destructor of Derived class
Destructor of Abstract class

虚析构函数的定义

析构函数是用来在对象生命周期结束时释放资源的特殊成员函数。

当基类的析构函数被声明为虚函数时,它就被称为虚析构函数。

虚析构函数的作用

C++中,当你用基类指针去删除派生类对象时,如果没有虚析构函数,就只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类中分配的资源(比如内存、文件句柄等)没有被正确释放,从而造成内存泄漏或其他资源未释放的问题。

虚析构函数通过动态绑定机制,确保在这种情况下,程序会正确调用派生类的析构函数,然后再调用基类的析构函数。这样就保证了所有相关的资源都能被正确释放。

虚析构函数通过动态绑定机制确保在通过基类指针删除派生类对象时,会正确调用派生类的析构函数。

推荐一下

https://github.com/0voice

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

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

相关文章

【UVM项目实战】异步fifo—uvm项目结构以及uvm环境搭建

本文章同步到我的个人博客网站&#xff1a;ElemenX-King&#xff1a;【UVM项目实战】异步fifo—uvm项目结构以及uvm环境搭建 希望大家能使用此网站来进行浏览效果更佳&#xff01;&#xff01;&#xff01; 目录 一、异步FIFO1.1 异步FIFO的定义1.2 亚稳态1.3 异步FIFO关键技术…

【通关函数的递归】--递归思想的形成与应用

目录 一.递归的概念与思想 1.定义 2.递归的思想 3.递归的限制条件 二.递归举例 1.求n的阶乘 2.顺序打印一个整数的每一位 三.递归与迭代 前言:上篇博文分享了扫雷游戏的实现&#xff0c;这篇文章将会继续分享函数的递归相关知识点&#xff0c;让大家了解并掌握递归的思…

【FAQ】针对于消费级NVIDIA GPU的说明

概述 本文概述 HP Anyware 在配备消费级 NVIDIA GPU 的物理工作站上的关​​键组件、安装说明和重要注意事项。 注意&#xff1a;本文档适用于 NVIDIA 消费级 GPU。NVIDIA Quadro 和 Tesla GPU 也支持 HP Anyware 在公有云、虚拟化或物理工作站环境中运行。请参阅PCoIP Graphi…

MyBatis操作数据库---从入门到理解

文章目录 关于MyBatis操作数据库MyBatis⼊⻔&#xff08;使用&#xff09;Mybatis操作数据库的步骤&#xff1a;配置数据库连接字符串使⽤MyBatis完成简单的增删改查操作注解xml 单元测试开启驼峰命名(推荐) 打印日志 关于MyBatis操作数据库 在之前的学习,我们了解到web应⽤程…

【HFP】蓝牙语音通话控制深度解析:来电拒接与通话终止协议

目录 一、来电拒接的核心流程与信令交互 1.1 拒接场景的分类与触发条件 1.2 HF 端拒接流程 1.3 AG 端拒接流程 二、通话终止流程&#xff1a;主动断开与异常中断 2.1 终止场景的界定 2.2 HF 端终止流程 2.3 AG 端终止流程 三、信令协议的核心要素&#xff1a;AT 命令与…

使用QML Tumbler 实现时间日期选择器

目录 引言相关阅读项目结构示例实现与代码解析示例一&#xff1a;时间选择器&#xff08;TimePicker&#xff09;示例二&#xff1a;日期时间选择器&#xff08;DateTimePicker&#xff09; 主窗口整合运行效果总结下载链接 引言 在现代应用程序开发中&#xff0c;时间与日期选…

智能吸顶灯/摄影补光灯专用!FP7195双通道LED驱动,高效节能省空间 !

一、双路调光技术背景与市场需求 随着LED照明技术的快速发展和智能照明需求的激增&#xff0c;双路调光技术正成为照明行业的重要发展方向。传统单路调光方案只能实现整体亮度的统一调节&#xff0c;而双路调光则能够实现对两个独立通道的精确控制。今天&#xff0c;由我来为大…

YOLOv11改进-双Backbone架构:利用双backbone提高yolo11目标检测的精度

一、引言&#xff1a;为什么我们需要双Backbone&#xff1f; 在目标检测任务中&#xff0c;YOLO系列模型因其高效的端到端检测能力而备受青睐。然而&#xff0c;传统YOLO模型大多采用单一Backbone结构&#xff0c;即利用一个卷积神经网络&#xff08;CNN&#xff09;作为特征提…

《逃离云端束缚,拥抱GPT本地部署》

《逃离云端束缚,拥抱GPT本地部署》 一、GPT 热潮与本地部署的兴起 自 OpenAI 推出 ChatGPT 以来,全球范围内掀起了一股人工智能的热潮,其强大的自然语言处理能力和广泛的应用场景,让人们对人工智能的未来充满了想象。GPT(Generative Pretrained Transformer)作为一种基于…

头歌之动手学人工智能-机器学习 --- PCA

目录 第1关&#xff1a;维数灾难与降维 第2关&#xff1a;PCA算法流程 任务描述 编程要求 测试说明 第3关&#xff1a;sklearn中的PCA 任务描述 编程要求 测试说明 第1关&#xff1a;维数灾难与降维 第2关&#xff1a;PCA算法流程 任务描述 本关任务&#xff1a;补充…

研0调研入门

一、Web of Science 使用教程 1. 访问与注册 访问入口&#xff1a;通过高校图书馆官网进入&#xff08;需IP权限&#xff09;&#xff0c;或直接访问 Web of Science官网。注册/登录&#xff1a;若机构已订阅&#xff0c;用学校账号登录&#xff1b;个人用户可申请试用或付费…

神经网络基础[ANN网络的搭建]

神经网络 人工神经网络&#xff08; Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络&#xff08;NN&#xff09;&#xff0c;是一种模仿生物神经网络结构和功能的计算模型。各个神经元传递复杂的电信号&#xff0c;树突接收到输入信号&#xf…

五、web自动化测试01

目录 一、HTML基础1、HTML介绍2、常用标签3、基础案例3.1 前端代码3.2 自动化测试 二、CSS定位1、css介绍2、案例3、代码优化 三、表单自动化1、案例2、元素属性定位 四、后台基础数据自动化1、登录1.1 id与class定位1.2 定位一组元素 2、商品新增 一、HTML基础 可参考学习 链…

数据库监控 | MongoDB监控全解析

PART 01 MongoDB&#xff1a;灵活、可扩展的文档数据库 MongoDB作为一款开源的NoSQL数据库&#xff0c;凭借其灵活的数据模型&#xff08;基于BSON的文档存储&#xff09;、水平扩展能力&#xff08;分片集群&#xff09;和高可用性&#xff08;副本集架构&#xff09;&#x…

STM32F407使用ESP8266实现阿里云OTA(中)

文章目录 前言一、程序分析二、程序讲解1. main函数2. Get_Version()函数3. esp_Init()函数4. Check_Updata()函数结语前言 从上一章STM32F407使用ESP8266实现阿里云OTA(上)中我们已经对连接阿里云和从阿里云获取升级包的流程非常的熟悉了。所以本章我们进行STM32的程序开发…

微信小程序 tabbar底部导航栏

官方文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar 一、常规菜单格式 在app.json 文件中配置&#xff0c;其他关键点详见官方文档&#xff0c;后续更新不规则图标的写法

Unity InputSystem触摸屏问题

最近把Unity打包后的windows软件放到windows触摸屏一体机上测试&#xff0c;发现部分屏幕触摸点击不了按钮&#xff0c;测试了其他应用程序都正常。 这个一体机是这样的&#xff0c;一个电脑机箱&#xff0c;外接一个可以触摸的显示屏&#xff0c;然后UGUI的按钮就间歇性点不了…

Linux Awk 深度解析:10个生产级自动化与云原生场景

看图猜诗&#xff0c;你有任何想法都可以在评论区留言哦~ 摘要 Awk 作为 Linux 文本处理三剑客中的“数据工程师”&#xff0c;凭借字段分割、模式匹配和数学运算三位一体的能力&#xff0c;成为处理结构化文本&#xff08;日志、CSV、配置文件&#xff09;的终极工具。本文聚…

免费版还是专业版?Dynadot 域名邮箱服务选择指南

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

旋转磁体产生的场-对导航姿态的影响

pitch、yaw、roll是描述物体在空间中旋转的术语&#xff0c;通常用于计算机图形学或航空航天领域中。这些术语描述了物体绕不同轴旋转的方式&#xff1a; Pitch&#xff08;俯仰&#xff09;&#xff1a;绕横轴旋转&#xff0c;使物体向前或向后倾斜。俯仰角度通常用来描述物体…