C++的继承和多态

继承和多态

  • 继承
    • 继承的权限
    • 继承的子父类访问
    • 派生类的默认成员函数
    • 菱形继承(C++独有)【了解】
    • 虚拟继承
    • 什么是菱形继承?菱形继承的问题是什么?
    • 什么是菱形虚拟继承?如何解决数据冗余和二义性的
    • 继承和组合的区别?什么时候用继承?什么时候用组合?
  • 多态
    • 构成多态的条件
    • 多态的原理
      • 虚函数表的打印
    • 虚函数的重写
      • 虚函数重写的第一个例外--- 析构函数的重写
      • 虚函数重写的第二个例外---协变
    • 重载、覆盖(重写)、隐藏(重定义)的对比
      • 重写和隐藏的详细解释
  • 抽象类
  • C++11的override和final
    • final
    • override
  • 面试问题
    • inline函数可以是虚函数吗?
    • 静态成员可以是虚函数吗?
    • 构造函数可以是虚函数吗?
    • 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    • 对象访问普通函数快还是虚函数更快?
    • 虚函数表是在什么阶段生成的,存在哪的?
    • 什么是抽象类?抽象类的作用?

继承

继承的权限

在这里插入图片描述

继承的子父类访问

在这里插入图片描述

派生类的默认成员函数

在这里插入图片描述

菱形继承(C++独有)【了解】

iostream就是菱形继承 可以去查库
在这里插入图片描述

虚拟继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用

当一个类从多个类中继承时,这些类又共同继承自另一个基类,这时可以使用虚拟继承来确保基类的共享实例。具体来说,就是在派生类声明中使用virtual关键字来继承基类,这样在进一步的派生中,基类的成员就会被共享,而不是重复复制。

例如,如果Father和Mother类都虚拟继承自GrandParent类,那么当GrandSon类继承FatherMother时,GrandSon对象中只会有一个GrandParent的实例,这样就避免了数据冗余。同时,由于虚拟基类的引入,对于基类成员的访问也变得明确,解决了二义性问题。

class GrandParent {
public:void sayHello() {std::cout << "Hello from GrandParent!" << std::endl;}
};class Father : virtual public GrandParent {
};class Mother : virtual public GrandParent {
};class GrandSon : public Father, public Mother {
};int main() {GrandSon son;son.sayHello(); // 输出 "Hello from GrandParent!"return 0;
}

什么是菱形继承?菱形继承的问题是什么?

菱形继承是一种多继承的特殊情况,它涉及四个类形成一个菱形结构。

在菱形继承中,存在一个基类,两个派生类继承这个基类,然后另一个类同时继承这两个派生类。这种继承方式在类的层次结构图中看起来像一个菱形,因此得名。

菱形继承的主要问题是数据冗余和二义性。

由于最底层的派生类继承了两个基类,而这两个基类又继承了同一个基类,所以会造成最顶部基类的两次调用。这会导致相同数据的重复存储,即冗余性。更重要的是,当访问某个继承自基类的属性或方法时,会产生歧义,因为不清楚应该访问哪个派生类中的版本,这就是所谓的二义性。

总而言之,菱形继承是多继承中特有的一种复杂情况,在设计类的继承关系时应谨慎使用,以避免引起数据冗余和二义性问题。

什么是菱形虚拟继承?如何解决数据冗余和二义性的

菱形虚拟继承是一种特殊的多继承方式,它通过虚拟基类来解决菱形继承中的数据冗余和二义性问题

在C++中,菱形虚拟继承是通过使用关键字virtual来实现的。当一个类从多个类中继承时,这些类又共同继承自另一个基类,这时可以使用虚拟继承来确保基类的共享实例。具体来说,就是在派生类声明中使用virtual关键字来继承基类,这样在进一步的派生中,基类的成员就会被共享,而不是重复复制。

例如,如果FatherMother类都虚拟继承自GrandParent类,那么当GrandSon类继承FatherMother时,GrandSon对象中只会有一个GrandParent的实例,这样就避免了数据冗余。同时,由于虚拟基类的引入,对于基类成员的访问也变得明确,解决了二义性问题。

总的来说,虽然菱形虚拟继承可以解决这些问题,但它也会增加代码的复杂性。因此,在设计类的继承结构时,应当谨慎考虑是否真的需要使用多继承和虚拟继承,以及它们带来的复杂性和可能的性能影响。

继承和组合的区别?什么时候用继承?什么时候用组合?

下面以表格形式对比继承和组合的区别以及它们的适用场景:

特性继承组合
定义继承是一种从现有类派生新类的关系。组合是指一个类包含另一个类的实例。
耦合性通常较高,因为子类与父类紧密相关。较低,因为类之间通过接口进行交互。
封装性可能破坏封装性,因为子类能访问父类保护成员。维护良好的封装性,只通过接口交互。
代码重用允许子类重用父类的代码和行为。通过聚合或包含实现代码重用。
多态性支持,子类可以覆盖或扩展父类方法。不直接支持,需要通过其他机制实现。
设计灵活性修改父类可能会影响所有子类。更灵活,整体与部分独立变化。
使用场景适用于“是一个”关系(如猫是动物)。适用于“有一个”关系(如车有引擎)。
示例Dog继承自MammalMammal继承自AnimalCar包含Engine对象作为其组成部分。

何时使用继承:

  • 当你想表达一种类型层级,例如,所有的猫都是哺乳动物,所有的哺乳动物都是动物。
  • 当子类需要父类的属性和方法,并且可能还需要在子类中添加额外的特性或重写父类的方法。

何时使用组合:

  • 当你想表达的是聚合关系,例如,一辆车有一个引擎,但车不是引擎的一种类型。
  • 当你希望保持类之间的松耦合,使得一个类的内部实现可以独立于使用它的类而变化。

在实际的软件开发中,组合通常被认为是比继承更有优势的设计选择,因为它提供了更好的灵活性和封装性。然而,在某些情况下,继承仍然是合适的,特别是在表示自然的层次关系时。

多态

构成多态的条件

继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

多态的原理

虚函数表的打印

class Base {
private:int _b=1;
public:Base():_b(10){++_b;}virtual void fun1() {cout << "Base::fun1" << endl;}virtual void fun2() {cout << "Base::fun2" << endl;}void fun3() {cout << "Base::fun3" << endl;}
};class Derive :public Base {
private:int _d = 2;
public:virtual void fun1() {cout << "Derive::fun1" << endl;}virtual void fun4() {cout << "Derive::fun4" << endl;}
};typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table) {for (int i = 0;table[i] != nullptr;++i) {printf("[%d]:%p->", i, table[i]);VF_PTR f = table[i];f();}cout << endl;
}
int main() {Base b;Derive d;PrintVFTable((*(VF_PTR**)&b));PrintVFTable((*(VF_PTR**)&d));return 0;
}

在这里插入图片描述

虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

虚函数重写的第一个例外— 析构函数的重写

基类与派生类析构函数的名字不同

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor

虚函数重写的第二个例外—协变

基类与派生类虚函数返回值类型不同

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)

重载、覆盖(重写)、隐藏(重定义)的对比

在C++中,重载(Overload)、覆盖(Override,也称为重写)和隐藏(Hide,也称为重定义)是三种不同的函数关系。它们的区别可以通过下表进行总结:

概念作用域参数列表返回类型
重载同一作用域必须不同可相同也可不同
覆盖/重写派生类与基类之间相同必须相同(C++11起,返回类型也可以被协变)
隐藏不同作用域(如基类与派生类)可以相同,也可以不同无特定要求

具体解释如下:

  1. 重载
  • 作用域:发生在同一作用域内,通常是同一个类中。
  • 参数列表:同名函数必须有不同的参数列表(参数类型、个数或顺序至少有一项不同)。
  • 返回类型:可以相同,也可以不同。
  1. 覆盖/重写
  • 作用域:发生在基类与派生类之间。
  • 参数列表:派生类中的函数必须与基类中的虚函数有完全相同的参数列表。
  • 返回类型:从C++11开始,返回类型可以是相同的,或者是派生类类型的派生类(协变返回类型)。
  1. 隐藏
  • 作用域:发生在不同作用域,例如基类与派生类中的非虚函数。
  • 参数列表:同名函数的参数列表可以相同,也可以不同。
  • 返回类型:没有特定的要求。

综上所述,重载允许在同一作用域内有多种接受不同参数的同名函数;覆盖/重写是指派生类重新定义了基类的虚函数,通常用于实现多态;而隐藏则是当派生类中的函数与基类中的函数同名时,无论参数列表是否相同,基类中的函数都会被隐藏。理解这些概念对于编写正确的C++面向对象程序至关重要。

重写和隐藏的详细解释

重写(Overriding):
当你在派生类中定义一个与基类中同名且函数签名(包括参数类型和返回类型)完全相同的虚函数时,你实际上是在提供一个新的实现。当通过基类的指针或引用调用这个函数时,C++运行时将动态地(在程序运行时)决定执行基类的版本还是派生类的版本,这是多态的一个特征。

class Base {
public:virtual void doSomething() {cout << "Base's doSomething" << endl;}
};class Derived : public Base {
public:virtual void doSomething() override { // 重写基类的方法cout << "Derived's doSomething" << endl;}
};

在这个例子中,Derived类重写了Base类的虚函数doSomething

隐藏(Hiding):
当派生类定义了一个与基类中同名的成员函数,哪怕是参数个数或类型不同,或者不是虚函数,基类的那个成员函数在派生类的对象上就无法直接访问了。这被称为隐藏。这不是多态的表现,而是简单的名字覆盖,这种情况下不会有运行时的动态调用。

class Base {
public:void doSomething() {cout << "Base's doSomething" << endl;}
};class Derived : public Base {
public:void doSomething(int x) { // 隐藏了基类的doSomethingcout << "Derived's doSomething with int" << endl;}
};

在这个例子中,Derived类隐藏了Base类的doSomething函数。

总结一下:

  • 重写是多态的一个体现,重写必须涉及到虚函数,函数签名必须相同,C++通过虚函数表来实现运行时的动态绑定。
  • 隐藏发生在派生类声明了一个与基类同名的函数后(而不管签名是否相同),此时通过派生类的对象将不能访问到基类中被同名函数隐藏的成员,除非显式指定作用域。隐藏不涉及虚函数或运行时的动态绑定。

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。**包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。**派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

C++11的override和final

final

final:修饰虚函数,表示该虚函数不能再被重写
我的理解是这个虚函数是父类特有的功能,不能被子类所继承。

class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒适" << endl;}//这里会报错说不能继承
};

override

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
我的理解是这功有点鸡肋,就是你在子类继承后面写override,override会帮你检查该函数是否是需要虚函数重写。

面试问题

inline函数可以是虚函数吗?

答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。

静态成员可以是虚函数吗?

答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

构造函数可以是虚函数吗?

答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

答:可以,并且最好把基类的析构函数定义成虚函数。

对象访问普通函数快还是虚函数更快?

答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

虚函数表是在什么阶段生成的,存在哪的?

答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

什么是抽象类?抽象类的作用?

答:参考(3.抽象类)。抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

平台工程与安全

平台工程不是为了取代DevOps&#xff0c;而是DevOps的进一步演进和发展。本文介绍了DevOps和平台工程&#xff0c;以及对于安全的意义。原文: Platform Engineering and Security: A Very Short Introduction 中国云南大理的日落 我是一名 DevOps 工程师&#xff0c;个人还是希…

Vue.js+SpringBoot开发社区买菜系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 数据中心模块2.1.2 菜品分类模块2.1.3 菜品档案模块2.1.4 菜品订单模块2.1.5 菜品收藏模块2.1.6 收货地址模块 2.2 可行性分析2.3 用例分析2.4 实体类设计2.4.1 菜品分类模块2.4.2 菜品档案模块2.4.3…

FRM模型十二:极值理论

目录 极值理论介绍GEVPOT 代码实现 极值理论介绍 在风险管理中&#xff0c;将事件分为高频高损、高频低损、低频高损、低频低损。其中低频高损是一种非常棘手的损失事件&#xff0c;常出现在市场大跌、金融体系崩溃、金融危机以及自然灾害等事件中。 由于很难给极端事件一个准…

嵌入式驱动学习第一周——git的使用

前言 本文主要介绍git的使用&#xff0c;包括介绍git&#xff0c;gitee&#xff0c;以及使用gitee创建仓库并托管代码 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xf…

阿里最新EMO:只需要提供一张照片和一段音频,即可生成会说话唱歌的AI视频

只要一张照片加上音频&#xff0c;就能让你说话唱歌&#xff0c;阿里做到了。 最近&#xff0c;阿里新上线了一款AI图片-音频-视频模型技术EMO&#xff0c;用户只需要提供一张照片和一段任意音频文件&#xff0c;EMO即可生成会说话唱歌的AI视频。以及实现无缝对接的动态小视频…

JavaScript-关于事件、事件流(捕获、冒泡)、事件源、常用事件

1.如何注册事件(如何绑定事件) ​ 何为注册事件&#xff0c;就是给元素添加事件&#xff0c;其方式有传统注册事件、方法监听注册事件。 0、1级事件&#xff08;传统注册事件&#xff09;不允许多个响应程序 我们在元素内或js内使用on的方式就是传统注册事件&#xff0c;这种形…

【论文阅读笔记】Explicit Visual Prompting for Low-Level Structure Segmentations

1.介绍 Explicit Visual Prompting for Low-Level Structure Segmentations 低级结构分割的显式视觉提示 2023年发表在IEEE CVPR Paper Code 2.摘要 检测图像中低级结构&#xff08;低层特征&#xff09;一般包括分割操纵部分、识别失焦像素、分离阴影区域和检测隐藏对象。虽…

有道翻译相关介绍

官网&#xff1a;有道翻译 (youdao.com) 翻文本与文档 长短句实时翻译&#xff0c;109种语言互译 支持医学、计算机、金融经济等专业领域翻译 提供42个专业术语库&上传自定义术语库 翻词汇 实时收录最新词汇&#xff0c;8种语言互译 完整收录新牛津、柯林斯、韦氏等权威…

贪心算法(算法竞赛、蓝桥杯)--修理牛棚

1、B站视频链接&#xff1a;A27 贪心算法 P1209 [USACO1.3] 修理牛棚_哔哩哔哩_bilibili 题目链接&#xff1a;[USACO1.3] 修理牛棚 Barn Repair - 洛谷 #include <bits/stdc.h> using namespace std; const int N205; int m,s,c,ans; int a[N];//牛的位置标号 int d[N…

基于springboot实现街球社区网站系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现街球社区网站系统演示 摘要 本文主要讲述了基于SpringBootVue模式的街球社区网站的设计与实现。这里所谓的街球社区网站是通过类似于百度贴吧之类的网上论坛使得所有的街球爱好者有一个可以互相交流的平台,并使所有用户可以在社区进行教学视频的观看以及相关…

【LeetCode】一周中的第几天+ 一年中的第几天

2023-12-30 文章目录 一周中的第几天方法一&#xff1a;模拟思路步骤 方法二&#xff1a;调用库函数方法三&#xff1a;调用库函数 [1154. 一年中的第几天](https://leetcode.cn/problems/day-of-the-year/)方法一&#xff1a;直接计算思路&#xff1a; 方法二&#xff1a;调用…

智慧灌区项目案例(甘肃省兰州市某重点灌区)

​甘肃省兰州市某重点灌区自上个世纪80年代建成后,灌溉面积达到30万亩,对推动当地农业发展发挥了重要作用。但长期以来,该灌区的水利管理仍主要依靠人工统计记录,缺乏实时监测和精细化管理。为实现灌区管理的现代化升级,甘肃水利局委托星创易联公司设计实施水利信息化项目。 项…

MAC M1 安装mongodb7.0.5 版本

1、进入官网 Download MongoDB Community Server | MongoDBDownload MongoDB Community Server non-relational database to take your next big project to a higher level!https://www.mongodb.com/try/download/community 2、选择版本 3、下载后解压 放到 /usr/local 并修改…

HarmonyOS—端云一体化组件

概述 DevEco Studio还为您提供多种端云一体化组件。集成端云一体化组件后&#xff0c;您只需进行简单配置即可向应用用户提供登录、支付等众多功能。 登录组件 您可使用端云一体化登录组件向应用用户提供登录和登出功能&#xff0c;目前支持帐号密码登录、手机验证码登录、以…

css【详解】—— 圣杯布局 vs 双飞翼布局 (含手写清除浮动 clearfix)

两者功能效果相同&#xff0c;实现方式不同 效果预览 两侧宽度固定&#xff0c;中间宽度自适应&#xff08;三栏布局&#xff09;中间部分优先渲染允许三列中的任意一列成为最高列 圣杯布局 通过左右栏填充容器的左右 padding 实现&#xff0c;更多细节详见注释。 <!DOCTYP…

【Linux C | 网络编程】getaddrinfo 函数详解及C语言例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

数据库进阶——如何提升数据库的安全性,以MySQL和Redis加固为例

目录 引出数据库加固加固思路MySQLRedis Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 数据库进阶——如何提升数据库的安全性&#xff0c;以MySQL和Redis加固为例 数据库加固 加固思路 账号配置 应按照用户分配账号&…

Linux进阶——系统安全,重要文件,加固系统的相关配置

目录 引出Linux系统安全一、重要文件二、帐户口令三、权限管理四、日志配置五、服务安全六、其他配置 缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Linux进阶——系统安全&#xff0c;重要文件&#xff0c;加固系统的相关配置 Linux系统安…

C习题002:澡堂洗澡【仅供参考】

问题 输入样例 在这里给出一组输入。例如&#xff1a; 2 5 1 3 3 2 3 3 输出样例 在这里给出相应的输出。例如&#xff1a; No代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 代码 #include<stdio.h> int main() {int N,W,s,t,p;int arr_s[…

Spring 学习记录

Spring 学习记录 1. Spring和SpringFrameWork1.1 广义的Spring2.1 狭义的Spring2.3 SpringFrameWork / Spring框架图 2. Spring IOC容器(即上图中的Core Container)2.1 相关概念 (IOC DI 容器 组件)2.2 Spring IOC容器的作用2.3 Spring IOC容器接口和具体实现类 3. Spring IOC …