当前位置: 首页 > news >正文

浅谈C++的继承与多态(静态绑定、动态绑定和虚函数等)

今天我们来谈谈C++的继承与多态😊😊😊,本篇的关键内容如下:

  • 继承的本质及其原理
  • 派生类的构造和析构过程
  • 重载、隐藏和覆盖
  • 类的向下或向上转型
  • 静态绑定与动态绑定
  • 虚函数对类的影响
  • 虚析构函数

下面,我们将对这几个有关于C++继承与多态的关键内容进行详述的论述

浅谈C++的继承与多态

    • 一、继承的本质及其原理
    • 二、派生类的构造和析构过程
    • 三、重载、隐藏和覆盖
    • 四、类的向下或向上转型
    • 五、静态绑定与动态绑定
    • **一个问题:thinking::thinking::thinking::一个类声明了虚函数后,当调用该虚函数时,一定是动态绑定吗?** **不一定 !**
    • 六、虚析构函数
    • **什么时候把基类的析构函数声明为虚函数呢?**
    • 七、虚函数对类的影响

一、继承的本质及其原理

在C++中,继承是一种面向对象编程思想的概念,允许一个类继承另一个类的属性和方法,同时也可以在此基础上添加新的属性和方法。究其本质,即:

  • 代码的复用
  • 在基类中给所有派生类提供一个统一的接口,让派生类重写,满足开闭原则

二、派生类的构造和析构过程

在定义的派生类中,由于派生类是从基类的基础上继承过来的,对应继承而来的成员的初始化和清理是由基类的构造函数和析构函数负责,而派生类的构造和析构函数则负责初始化和清理派生类中特定的部分,所以在派生类调用构造函数需要初始化基类里的成员变量时,不能直接指定,而是需要通过基类的构造函数来进行初始化,如下:

class Base
{
public:Base(int d) :ma(d) { cout << "Base()" << endl; }~Base() { cout << "~Base()" << endl; }
protected:int ma;
};class Derive :public Base
{
public://Derive(int d = 20) :ma(d), mb(d) {cout << "Derive()" << endl;}//此句会出现报错:"ma" 不是类 "Derive" 的非静态数据成员或基类Derive(int d = 20) :Base(d), mb(d) {cout << "Derive()" << endl;}~Derive() { cout << "~Derive()" << endl; }
private:int mb;
};

这里我们给出一段关于派生类构造和析构过程的主要描述:

  • 派生类调用基类的构造函数,初始化从基类继承而来的成员
  • 调用派生类自己的构造函数,初始化派生类自己特有的成员
  • 调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆空间、文件等)
  • 调用基类的构造函数,释放派生类内存中从基类继承而来的成员可以占用的外部资源(堆空间、文件等)
int main()
{Derive m(20);  //可以自己允许一遍,查看程序允许结果return 0;
}

三、重载、隐藏和覆盖

重载: 一组函数重载,必须处在同一个作用域中,且函数名相同,参数列表不同
隐藏: 隐藏其实就是隐藏作用域,即,在继承结构中,派生类的同名成员,把基类的同名成员隐藏掉了
覆盖: 如果派生类中定义的方法与在基类继承而来的方法,在返回值、函数名、参数列表都相同的前提下,且基类的实现方法为virtual虚函数,那么派生类的这个方法就会被自动的处理成虚函数,本质上是虚函数表上的虚函数地址的覆盖,详见后文!

若我们在上面的Base类和Derive中添加方法 void show() ,在派生类调用show函数时会优先在自己类中寻找对应的函数方法,即会打印Derive::show(),基类的show方法被隐藏了

class Base
{...void show() { cout << "Base::show()" << endl; }...
};
class Derive :public Base
{void show() { cout << "Derive::show()" << endl; }...
};int main()
{Derive m(20);m.show();  //优先找派生类自己show成员return 0;
}

四、类的向下或向上转型

在C++中,派生类对象转化为基类对象、基类对象转化为派生类对象的情形可以描述为:

  • 向上转型: 派生类对象(指针) -> 基类对象(指针) YES
  • 向下转型: 基类对象(指针) -> 派生类对象(指针) NO

由于派生类是由基类继承而来的,其给它分配地址空间大于基类的地址空间,故可以将派生类对象(指针)转化为基类对象(指针),即Base* pb = &m,而不可以可以将基类类对象(指针)转化为派生类对象(指针),即Derive* pd = &m;,如下图所示:
在这里插入图片描述
对于 派生类 -》 基类: 相当于切片,红色部分内存在基类指针访问不了,也不会被访问
对于 基类 -》 派生类: 派生类申请的地址空间大于基类,一旦派生类对象/指针访问红色部分时,由于基类对象是没有这块空间的,直接会发生非法访问问题
在这里插入图片描述

五、静态绑定与动态绑定

静态绑定和动态绑定是C++中两种不同的绑定方式

  • 静态绑定: 在编译时进行的绑定。它根据函数调用时的静态类型来确定要调用的函数。静态绑定适用于非虚函数,它会默认绑定到函数定义中的相应代码。
  • 动态绑定: 在运行时进行的绑定。它根据函数调用时的动态类型来确定要调用的函数。动态绑定适用于虚函数,它允许在运行时根据实际对象类型来调用相应的函数。这样可以实现多态性,即不同对象调用同名函数时可以执行不同的操作。
#include <iostream>class Base {
public:void print() {std::cout << "Base class" << std::endl;}virtual void display() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void print() {std::cout << "Derived class" << std::endl;}void display() {std::cout << "Derived class" << std::endl;}
};int main() {Base* d = new Derived();d->print(); // 静态绑定,调用Base类的print函数/*mov eax, dword ptr[pb]mov ecx, dword ptr[eax]call ecx;(虚函数地址)  动态(运行时期)的绑定(函数的调用)*/d->display(); // 动态绑定,调用Derived类的display函数return 0;
}

d->print() 调用的是静态绑定,因为 print() 函数在基类中不是虚函数。即使 d 指向的对象实际类型是 Derived 类,编译器也会根据指针类型(Base*)来静态绑定调用基类的 print() 函数。所以输出为 Base class

d->display() 这里调用的是动态绑定,因为 display() 函数是虚函数。在运行时,实际调用的是指向的对象的类型的版本,即RTTI (run-time type information) ,即 Derived 类中的 display() 函数。所以输出为 Derived class

一个问题🤔🤔🤔:一个类声明了虚函数后,当调用该虚函数时,一定是动态绑定吗? 不一定 !

动态绑定的实现通常涉及虚函数表(vtable)的使用。每个包含虚函数的类都会有一个虚函数表,其中包含了指向虚函数的指针。子类会继承父类的虚函数表,并在其自己的虚函数表中重写父类的虚函数指针。这样,当通过指针或引用调用虚函数时,会根据对象的实际类型找到对应的虚函数指针,并调用正确的函数。
然而,如果直接通过对象调用虚函数,编译器会根据对象的静态类型来进行绑定,即根据对象的声明类型来调用虚函数,而这就是静态绑定,因为编译器在编译时已经确定了应该调用哪个函数,不需要在运行时根据对象的实际类型进行判断。因此,在通过指针或引用调用虚函数时,会进行动态绑定,根据对象的实际类型确定调用哪个函数;而在直接通过对象调用虚函数时,会进行静态绑定,根据对象的声明类型确定调用哪个函数
在这里插入图片描述

六、虚析构函数

虚析构函数是一种特殊的析构函数,用于实现多态性。它允许在基类指针指向派生类对象时,使用基类指针来调用派生类对象的析构函数,从而正确地释放派生类对象的资源,我们先来看看下面几个问题

我们来看看虚函数所依赖的条件:

  • 虚函数能产生地址,存储在vftable虚函数表中
  • 对象必须存在(vfptr -> vftable -> 虚函数地址)

哪些函数不能声明为虚函数?

  • 构造函数:构造函数在对象创建和销毁的过程中是特殊的,在构造函数中的所有函数都是静态绑定的,不能声明为virtual
  • 静态成员函数static:虚函数是通过对象访问的,而静态成员函数没有 this 指针,是直接通过类名访问的,不能被继承和覆盖
  • 内联函数:内联函数在编译时会直接将函数体嵌入到函数调用点,没有函数调用的开销。而虚函数是通过虚函数表来确定的,无法进行内联

什么时候把基类的析构函数声明为虚函数呢?

当基类指针(或引用)指向在堆上通过new创建的派生类对象时,派生类对象也会分配一份外部空间。如果基类的析构函数没有声明为虚函数,在准备使用delete删除基类指针时,会发生静态绑定。这样,基类对象会调用基类的析构函数,而派生类对象则无法调用其自己的析构函数,导致内存泄漏。

class Base
{
public:Base(int d) :ma(d) { cout << "Base()" << endl; }~Base() { cout << "~Base()" << endl; }virtual void show() { cout << "Base::show()" << >endl; }
protected:int ma;
};class Derive :public Base
{
public:Derive(int d) :Base(d), mb(d), ptr(new int(d)){cout << "Derive()" << endl;}// 基类的析构函数是virtual,那么派生类的析构>函数自动变成virtual~Derive(){delete ptr;cout << "~Derive()" << endl;}void show() { cout << "Derive::show()" << endl; }private:int mb;int* ptr;
};int main()
{Base* pb = new Derive(10);pb->show();delete pb;return 0;
}     

在将基类的析构函数声明为虚函数后,当使用delete删除基类指针时,由于基类的析构函数是虚函数,会发生动态绑定。这样,派生类的析构函数会自动成为虚析构函数。在执行delete时,通过虚函数表指针(vfptr)找到虚函数表(vftable),将基类在虚函数表上的虚析构函数覆盖为派生类的虚析构函数。这样就会先调用派生类的析构函数,再调用基类的虚构函数进行释放。

七、虚函数对类的影响

  1. 一个类里面定义了虚函数,那么编译阶段,编译器会给这个类生成唯一的虚函数表 vftable 主要存放的是RTTI指针虚函数地址,当程序运行时,每一张虚函数表都会加载到内存的.rodate区(只读,不能写)

  2. 一个类中定义了虚函数,那么这个类定义的对象在程序运行时,内存中开始的部分多存储一个vfptr虚函数指针,指向相同类型的虚函数表vftable,一个类型定义的n个对象,它们的 vfptr 指向的都是同一张虚函数表

  3. 一个类里的虚函数个数,不影响对象内存的大小(vfptr),影响的是虚函数表的大小

  4. 如果派生类中的方法和基类继承而来的某个方法的返回值、函数名、参数列表相同,而基类的方法是virtual虚函数,那么派生类的这个方法会自动被处理成虚函数,将虚函数表中的原来的虚函数地址覆盖成派生类的虚函数地址


🌻🌻🌻以上就是有关浅浅谈C++的继承与多态(静态绑定、动态绑定和虚函数等)的内容,如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏 🌻🌻🌻

相关文章:

浅谈C++的继承与多态(静态绑定、动态绑定和虚函数等)

今天我们来谈谈C的继承与多态&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;本篇的关键内容如下&#xff1a; 继承的本质及其原理派生类的构造和析构过程重载、隐藏和覆盖类的向下或向上转型静态绑定与动态绑定虚函数对类的影响虚析构函数 下面&#xff0c;我们将对这…...

【无人机综合考试题】

1.请选择出哪一个功能选项&#xff0c;在手动遥控飞行时&#xff0c;可以改变各通道的操作灵敏度? 行程比例在手动遥控飞行时&#xff0c;可以改变各通道的操作灵敏度 用于起降的遥控器中 THR、ELE 通道分别控制多旋翼无人机的什么运动? AIL(左、右移动)RUD(左、右水平旋转…...

JS精度计算的几种解决方法,1、转换成整数计算后再转换成小数,2、toFixed,3、math.js,4、bignumber.js,5、big.js

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、转换成整数计算后再转换成小数二、toFixed三、math.js四、bignumber.js五、big.js总结 前言 原始计算 let aNum 6.6 0.3;let bNum 6.6 - 0.2;let cNum 6.6 * 0.3;let dNum 6.6 / 0.2;console.log(…...

v77.递归

理解&#xff1a; 函数直接或者间接地调用自身&#xff1b;并且有边界条件。 1&#xff1a; #include <stdio.h> int main() {int result fun(3);printf("%d",result);return 0 ; } int fun(int num) {if(num 1)return num;return num fun(num-1); }思路…...

Spring Cloud微服务功能及其组件详细讲解

Spring Cloud微服务功能及其组件详细讲解 文章目录 Spring Cloud微服务功能及其组件详细讲解前言&#xff1a;什么是微服务&#xff1f;一、Spring Cloud原理简述二、核心组件1、服务发现——Nacos/Eureka/Consul1&#xff09;Nacos服务发现2&#xff09;Eureka服务发现3&#…...

(三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练

这里写目录标题 一、colmap解算数据放入高斯1. 将稀疏重建的文件放入高斯2. 将稠密重建的文件放入高斯 二、vkitti数据放入高斯 一、colmap解算数据放入高斯 运行Colmap.bat文件之后&#xff0c;进行稀疏重建和稠密重建之后可以得到如下文件结构。 1. 将稀疏重建的文件放入高…...

4635: 【搜索】【广度优先】回家

题目描述 小 H 在一个划分成了nm 个方格的长方形封锁线上。 每次他能向上下左右四个方向移动一格&#xff08;当然小 H 不可以静止不动&#xff09;&#xff0c; 但不能离开封锁线&#xff0c;否则就被打死了。 刚开始时他有满血 6 点&#xff0c;每移动一格他要消耗 1 点血量…...

Uibot6.0 (RPA财务机器人师资培训第1天 )RPA+AI、RPA基础语法

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北之前的几篇博客&#xff0c;友友们我们即将开展新课的学习~…...

【吊打面试官系列】Redis篇 -Redis集群的主从复制模型是怎样的?

大家好&#xff0c;我是锋哥。今天分享关于 【Redis集群的主从复制模型是怎样的&#xff1f;】 面试题&#xff0c;希望对大家有帮助&#xff1b; Redis集群的主从复制模型是怎样的&#xff1f; 为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用&#xff0c;所…...

高效的二进制列化格式 MessagePack 详解

目录 MessagePack 序列化原理 MessagePack 数据类型及编码方式 MessagePack 序列化与反序列化过程 MessagePack 的优势 应用场景 注意事项 小结 MessagePack &#xff08;简称 msgPack&#xff09;是一种高效的二进制序列化格式&#xff0c;可以将各种数据类型&#xff…...

鸿蒙Harmony应用开发—ArkTS-if/else:条件渲染

ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使用if、else和else if渲染对应状态下的UI内容。 说明&#xff1a; 从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使用。 使用规则 支持if、else和else if语句。 if、else if后跟随的条件语句…...

JAVA 100道题(14)

14.使用LinkedList实现一个简单的堆栈&#xff08;Stack&#xff09;数据结构。 下面是一个简单的Java程序&#xff0c;使用LinkedList来实现一个堆栈&#xff08;Stack&#xff09;数据结构。在这个程序中&#xff0c;我们定义了一个MyStack类&#xff0c;它包含了一些基本的堆…...

STM32+ESP8266水墨屏天气时钟:简易多级菜单(数组查表法)

项目背景 本次的水墨屏幕项目需要做一个多级菜单的显示&#xff0c;所以写出来一起学习&#xff0c;本篇文章不单单适合于水墨屏&#xff0c;像0.96OLED屏幕也适用&#xff0c;区别就是修改显示函数。 设计思路 多级菜单的实现&#xff0c;一般有两种实现的方法 1.通过双向…...

数学建模综合评价模型与决策方法

评价方法主要分为两类&#xff0c;其主要区别在确定权重的方法上 一类是主观赋权法&#xff0c;多次采取综合资讯评分确定权重&#xff0c;如综合指数法&#xff0c;模糊综合评判法&#xff0c;层次评判法&#xff0c;功效系数法等 另一类是客观赋权法&#xff0c;根据各指标…...

window下安装并使用nvm(含卸载node、卸载nvm、全局安装npm)

window下安装并使用nvm&#xff08;含卸载node、卸载nvm、全局安装npm&#xff09; 一、卸载node二、安装nvm三、配置路径和下载源四、使用nvm安装node五、nvm常用命令六、卸载nvm七、全局安装npm、cnpm八、遇到的问题 nvm 全名 node.js version management&#xff0c;顾名思义…...

Mysql——基础命令集合

目录 前期准备 先登录数据库 一、管理数据库 1.数据表结构解析 2.常用数据类型 3.适用所有类型的修饰符 4.使用数值型的修饰符 二、SQL语句 1.SQL语言分类 三、Mysql——Create,Show,Describe,Drop 1.创建数据库 2.查看数据库 3.切换数据库 4.创建数据表 5.查看…...

记录一次流相关故障

记录一次流相关故障 1、项目中有个JSON字典文件&#xff0c;通过流的方式加载进来&#xff0c;写了个输入流转字符串的方法&#xff0c;idea开发环境下运行一切正常&#xff0c;打成jar或者war包运行时&#xff0c;只能加载出部分数据&#xff0c;一开始怀疑过运行内存分配过小…...

linux源配置:ubuntu、centos;lspci与lsmod命令区别

1、ubuntu源配置 1&#xff09;先查电脑版本型号: lsb_release -c2&#xff09;再编辑源更新&#xff0c;源要与上面型号对应 参考&#xff1a;https://midoq.github.io/2022/05/30/Ubuntu20-04%E6%9B%B4%E6%8D%A2%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E6%BA%90/ /etc/apt/…...

面试算法-88-反转链表

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 解 class Solution {public ListNode reverseList(ListNode head) {if(head null || hea…...

如何在个人Windows电脑搭建Cloudreve云盘并实现无公网IP远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...