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++面向对象程序至关重要。

抽象类

在虚函数的后面写上 =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.抽象类)。抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3如何使用Pinia状态管理库与持久化

大家好&#xff0c;我是你们的好朋友咕噜铁蛋&#xff01;今天我将和大家分享如何在Vue3中使用Pinia状态管理库以及实现状态持久化的方法。作为一个Vue开发者&#xff0c;我们知道状态管理在大型应用程序中起着至关重要的作用。而Pinia作为Vue3推荐的状态管理库之一&#xff0c…

【论文笔记】Attention Is All You Need

【论文笔记】Attention Is All You Need 文章目录 【论文笔记】Attention Is All You NeedAbstract1 Introduction2 Background补充知识&#xff1a;软注意力 soft attention 和硬注意力 hard attention&#xff1f;补充知识&#xff1a;加法注意力机制和点乘注意力机制Extende…

HCIA-Datacom实验指导手册:6 构建基础 WLAN 网络

HCIA-Datacom实验指导手册&#xff1a;6 构建基础 WLAN 网络 一、实验介绍&#xff1a;二、实验拓扑&#xff1a;三、实验目的&#xff1a;四、配置步骤&#xff1a;1.掌握ap上线的配置方式和上线过程。ac配置验证 步骤 2 掌握隧道模式和旁挂模式下ac的配置。步骤 3 掌握查看ap…

android高级面试题2020,这套Github上40K+star面试笔记

前言 这里整理的是一些与技术没有直接关系的面试题&#xff0c;但是能够考察你的综合水平&#xff0c;所以不要以为不是技术问题&#xff0c;就不看&#xff0c;往往有时候就是这样一些细节的题目被忽视&#xff0c;而错过了一次次面试机会。 想要成为一名优秀的Android开发&…

生成服从伽马分布的随机样本np.random.gamma()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 生成服从伽马分布的随机样本 np.random.gamma() 选择题 关于以下代码输出的结果说法正确的是&#xff1f; import numpy as np import seaborn as sns a np.random.gamma(shape2,scale1.0,si…

WordPress通过宝塔面板的入门安装教程【保姆级】

WordPress安装教程【保姆级】【宝塔面板】 前言一&#xff1a;安装环境二&#xff1a;提前准备三&#xff1a;域名解析四&#xff1a;开始安装五&#xff1a;安装成功 前言 此教程适合新手&#xff0c;即使不懂代码&#xff0c;也可轻松安装wordpress 一&#xff1a;安装环…

时间管理大师速成(程序员版)

01 时间管理的重要性 管理时间有几个主要的原因&#xff1a; 时间和生活质量&#xff1a;时间是我们拥有的最宝贵的资源之一&#xff0c;管理好时间会直接影响我们的生活质量。高效的时间管理可以让我们开展日常活动&#xff0c;实现目标&#xff0c;并拥有休闲和休息的时间。 …

【虹科干货】以服务为中心的IT基础设施如何优化网络分析?

文章速览&#xff1a; 发现和识别故障实时数据分析数据包分析数据包快速捕获和解码 随着基础设施环境的快速变化和技术的不断进步&#xff0c;用户数量和IT基础设施流量迅速增加&#xff0c;服务故障的数量也相应增加。此时&#xff0c;服务中断不仅会带来直接的不便&#xf…

苍穹外卖学习 Day10 Day11 Day12

前言 用于记录苍穹外卖Day10、Day11、Day12的学习 Day10 订单状态定时处理 来电提醒 客户催单 订单状态定时处理 Spring Task Spring Task是一个任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑&#xff08;定时自动执行某段Java代码&#xff09; cron表…

小程序动态调试-解密加密数据与签名校验

前言&#xff1a; 微信小程序的加密与验签早前大多数情况&#xff0c;要么就是逆向获取源码而后拿到加密秘钥&#xff0c;要么就是逆向拿到源码后使用腾讯自带的小程序开发者功能进行动态调试模拟&#xff0c;今天介绍一款志远大佬的开源工具—WeChatOpenDevTool 工具下载地址…

01 MySQL之连接

1. 连接 1.0 基础认知 多表(主表)和一表(从表的区别): 多表一般是主表&#xff0c;一般存储主要数据&#xff0c;每个字段都可能存在重复值&#xff0c;没有主键&#xff0c;无法根据某个字段定位到准确的记录&#xff1b; 一表一般是从表&#xff0c;一般存储辅助数据&…

点云数据处理常用外部库(C++/Windows)的项目配置

一、点云数据处理常用外部库&#xff08;C版本&#xff09;的下载安装与项目配置 &#xff08;一&#xff09;PCL 基于VS2019编程平台的PCL外部库下载安装及项目配置已有大量博客&#xff0c;本文不再赘述。具体下载安装及项目配置流程可参考外部库编译配置参考资料/*1*/ 。需…

redis-RedisTemplate.opsForGeo 的geo地理位置及实现附近的人的功能

redis内部使用的是 zset 数据结构存储&#xff0c;如下 import cn.huawei.VideoApplication; import cn.huawei.domain.Jingqu; import cn.huawei.service.JingquService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired…

Redis 协议与异步方式

redis pipeline 模式 redis pipeline 是一个客户端提供的机制&#xff0c;与 redis 无关。pipeline 不具备事务性。目的&#xff1a;节约网络传输时间。通过一次发送多条请求命令&#xff0c;从而减少网络传输时间。 时间窗口限流 系统限定某个用户的某个行为在指定的时间范围…

SpringBoot项目中如何结合Mybatis进行数据库查询

在Spring Boot项目中使用Mybatis进行数据库操作是一种常见的实现方式。下面我将展示如何在Spring Boot项目中整合Mybatis。这个示例将包括几个主要部分&#xff1a;项目依赖配置、配置文件、实体类、Mapper接口及其XML配置文件、服务类、以及一个简单的控制器。 1. 项目依赖配…

一键安装|卸载 mysql 8.2.0 shell脚本

场景&#xff1a;为了在无网、外网 mysql 安装方便&#xff0c;这里分享一个自己编写得 shell脚本 这里以当前最新版 mysql 8.2.0&#xff1b;centos-7 二进制包下载&#xff1a; 下载地址 mysql_install.sh #!/bin/bash # 解压安装包 tar -xf mysql-8.2.0-linux-glibc2.17-x8…

opencv中两个LSD直线检测算法的区别与应用

opencv中两个LSD直线检测算法的区别与应用 同样是Line Segment Detector(lsd)算法&#xff0c;opencv中提供了两种实现&#xff0c;并且位于不同的模块。下面分别介绍它们的使用方法&#xff1a; 1. LineSegmentDetector 由于源码许可证问题 OpenCV 3.4.6-3.4.15、4.1.0-4.5.…

IDEA类和方法注释模板设置

一、概述 IDEA自带的注释模板不是太好用&#xff0c;我本人到网上搜集了很多资料系统的整理了一下制作了一份比较完整的模板来分享给大家&#xff0c;我不是专业玩博客的&#xff0c;写这篇文章只是为了让大家省事。 这里设置的注释模板采用Eclipse的格式&#xff0c;下面先贴…

【深度优先搜索】【图论】【推荐】332. 重新安排行程

作者推荐 动态规划的时间复杂度优化 本文涉及知识点 深度优先搜索 图论 LeetCode332. 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&a…

C++:常量表达式

C11开始constexpr作为一种声明&#xff0c;为编译器提供了在编译期间确认结果的优化建议&#xff0c;满足部分编译期特性的需求 constexpr和const区别 int b10; const int ab; //运行成功 constexpr int cb; //编译器报错&#xff0c;b的值在编译期间不能确定 const int size1…