C++笔记---继承(下)

embedded/2024/9/19 4:18:39/ 标签: c++, 笔记, 开发语言

1. 无法被继承的类

要实现无法被继承的类有两种方式:

C++98及其之前:将父类的构造函数设置为private成员。

C++11及其之后:使用final关键字修饰父类。

将构造函数设置为private是因为:子类的构成必须调用父类的构造函数,但是父类的构成函数私有化以后,子类看不见就不能调用了,那么子类就无法实例化出对象。 

// C++11的⽅法
class Base final
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的⽅法/*Base(){}*/
};class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b;Derive d;return 0;
}

2. 友元关系与继承

父类的友元关系不能被继承。也就是说,也就是说父类友元不能访问子类私有和保护成员。

这一点很好记忆:

父类进行友元声明  --->  这个函数/类是我的好友。

子类  --->  我父亲的好友是我的叔叔而不是好友。

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};class Student : public Person
{
protected:int _stuNum; // 学号
};void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}int main()
{Person p;Student s;// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员// 解决⽅案:Display也变成Student 的友元即可Display(p, s);return 0;
}

 3. 静态成员与继承

父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

class Person
{
public:string _name;static int _count;
};int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份cout << &p._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的// 说明派⽣类和基类共⽤同⼀份静态成员cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}

4. 多继承以及菱形继承问题

4.1 多继承

单继承:一个子类类只有一个直接父类时称这个继承关系为单继承。

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的父类在前面,后继承的父类在后面,子类成员在放到最后面。

class Assistant : public Student, public Teacher

 4.2 菱形继承

菱形继承是多继承的一种特殊情况。

菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。

支持多继承就一定会有菱形继承,像Java就直接不支持多继承,规避掉了这里的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。

class Person
{
public:string _name; // 姓名
};class Student : public Person
{
protected:int _num; //学号
};class Teacher : public Person
{
protected:int _id; // 职⼯编号
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};int main()
{// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;a._name = "peter";// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}

4.3 虚继承

虚继承是为了解决菱形继承而提出的,像Person这样被重复继承的类可以用virtual关键字来进行继承:

// 使用虚继承Person类
class Student : virtual public Person
{
protected:int _num; //学号
};// 使用虚继承Person类
class Teacher : virtual public Person
{
protected:int _id; // 职⼯编号
};// 教授助理
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};

当两个虚继承了同一个爷爷类(菱形的上顶点)的类被孙子类(菱形的下顶点)继承时,这两个子类的共同父类(爷爷类)就只会被孙子类继承一次。

此时,Person由于只被继承了一次,所以不再适合放到Student内部或Teacher内部,而是被放到整个对象的最下面。

在调用构造函数时,孙子类的初始化列表中会优先调用爷爷类的构造函数,然后是先继承的类的构造函数,后继承的类的构造函数。

而爸爸类初始化列表中对爷爷类构造函数的调用会失效。

很多人说C++语法复杂,其实多继承就是一个体现。

有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有一些损失,所以最好不要设计出菱形继承

多继承可以认为是C++的缺陷之一,后来的一些编程语言都没有多继承,如Java。

我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,无论是使用还是底层都会复杂很多。当然有多继承语法支持,就一定存在会设计出菱形继承,像Java是不支持多继承的,就避开了菱形继承。

5. 指针偏移问题

上一篇提到,子类对象可以给父类指针赋值。

在上面,我们已经见识过多继承对象内部成员的分布,当我们用Assistant对象给Teacher对象的指针赋值时,得到的地址实际上是Teacher成员开始的位置。

在下面的这个例子中,我们可以看到p1 == p3 != p2的结果。

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 
{ public: int _d; };int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

6. IO库中的菱形虚拟继承

template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};

 7. 继承和组合

• public继承是一种is - a的关系。也就是说每个派生类对象都是一个基类对象。

• 组合是一种has - a的关系。假设B组合了A,每个B对象中都有一个A对象。

• 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white - box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对派生类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。


• 对象组合是类继承之外的另⼀种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black - box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is - a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is - a)也适合组合(has - a),就用组合。


http://www.ppmy.cn/embedded/113025.html

相关文章

C++学习笔记(26)

七 、显示字符串中的字符 从界面上输入一个字符串&#xff08;C 风格&#xff09;&#xff0c;把字符串中的每个字符显示出来&#xff0c;如果输入的是"abc"&#xff0c;要求&#xff1a; 1&#xff09;正序显示&#xff1a;a b c 2&#xff09;逆序显示&#xff1a;…

NC 矩阵最长递增路径

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个 n 行…

【SQL】百题计划:SQL最基本的判断和查询。

[SQL]百题计划 Select product_id from Products where low_fats "Y" and recyclable "Y";

车辆重识别(关于卷积神经网络一些资料)2024/9/11

关于卷积神经网络的介绍 一&#xff0c;全连接神经网络 1&#xff0c;全连接神经网络的整体结构 X代表左边输入的数据&#xff08;向量或者矩阵等等&#xff09;&#xff0c;Y代表模型对数据处理之后的结果&#xff0c;中间的节点都可以算作为隐藏层。 2&#xff0c;全连接神经…

【Linux】进程调度与切换

【Linux】进程调度与切换 1. 基本概念2. 进程切换3. 进程调度3.1运行队列实现优先级设计3.2 处理效率问题3.3 活动队列与过期队列3.4 如何解决饥饿问题3.5 active指针和expired指针 1. 基本概念 竞争性: 系统进程数目众多&#xff0c;而CPU资源只有少量&#xff0c;甚至1个&am…

【乐吾乐大屏可视化组态编辑器】API接口文档(pgsql)

API接口文档&#xff08;pgsql&#xff09; 在线使用&#xff1a;https://v.le5le.com/ 采用前后端分离架构&#xff0c;乐吾乐后端服务提供一整套完整的web组态编辑器的所有数据接口&#xff0c;包含2D/3D图纸接口服务、文件接口服务和用户接口服务等&#xff0c;安装包版本…

通信工程学习:什么是GPON吉比特无源光网络

GPON&#xff1a;吉比特无源光网络 GPON&#xff08;Gigabit-Capable Passive Optical Network&#xff0c;吉比特无源光网络&#xff09;是一种基于ITU-T G.984.x标准的最新一代宽带无源光综合接入技术。该技术以其高带宽、高效率、大覆盖范围和用户接口丰富等特点&#xff0c…

tcp线程进程多并发

tcp线程多并发 #include<myhead.h> #define SERPORT 8888 #define SERIP "192.168.0.118" #define BACKLOG 20 typedef struct { int newfd; struct sockaddr_in cin; }BMH; void *fun1(void *sss) { int newfdaccept((BMH *)sss)->newfd; …

Java 枚举 新特性

Java 枚举&#xff08;enum&#xff09;自JDK 1.5引入以来&#xff0c;随着版本的升级不断增强。本文将回顾枚举的演进&#xff0c;尤其是结合switch语句的应用&#xff0c;展示枚举如何在现代Java中变得更加灵活。 1. JDK 1.5&#xff1a;Java 枚举的诞生 在JDK 1.5之前&…

新手教学系列——用Nginx将页面请求分发到不同后端模块

在当今的Web开发中,前后端分离架构已经成为主流,尤其是大型应用项目。前端可以通过Vue这样的框架来统一管理页面和用户交互,而后端则通常会拆分成多个微服务模块,以便应对不同业务需求和功能扩展。在这样的架构下,Nginx作为一个高效、灵活的Web服务器,能够帮助我们将前端…

探索pytorch数据集中Mnist数据集的数据格式

1 问题 1.1安装pytorch时&#xff0c;由于使用的vscode编译器&#xff0c;所以采用pip进行安装&#xff0c;但是遇到pytorch版本与python版本不对应的问题。 1.2探索pytorch数据集中Mnist数据集的数据格式。 2 方法 2.1 首先查看自己电脑能够适应的pytorch版本2.2 打开pytorch的…

Windows与Linux下 SDL2的第一个窗口程序

Windows效果和Linux效果如下&#xff1a; 下面是代码&#xff1a; #include <stdio.h> #include "SDL.h"int main(int argc, char* argv[]) { // 初始化SDL视频子系统if (SDL_Init(SDL_INIT_VIDEO) ! 0){// 如果初始化失败&#xff0c;打印错误信息printf(&…

python植物大战僵尸项目源码【免费】

植物大战僵尸是一款经典的塔防游戏&#xff0c;玩家通过种植各种植物来抵御僵尸的进攻。 源码下载地址&#xff1a; 植物大战僵尸项目源码 提取码: 8muq

raksmart的G口大流量服务器怎么样?

RAKsmart的G口大流量服务器以其高性能、高可用性、灵活配置和全球覆盖等特点&#xff0c;成为许多企业和个人用户的理想选择。以下是对raksmart G口大流量服务器的具体介绍&#xff1a; 1. 服务特点&#xff1a; RAKsmart提供多种类型的G口大流量服务器&#xff0c;包括流媒体专…

阿里云人工智能ACP错题整理.txt

1、TextRank是一种关键词抽取和文档摘要的排序算法&#xff0c;由谷歌的网页重要性排序算法PageRank算法改进而来&#xff0c;利用文本内部的词语间的语义便可以抽取关键词&#xff0c;它能够从一个给定的文本中抽取出该文本的关键词、关键词组&#xff0c;并使用抽取式的自动文…

Spring 源码解读:手动实现Spring事件机制

引言 事件驱动的编程模式是现代软件架构中的一个重要概念&#xff0c;它允许不同组件之间通过发布事件和监听事件来实现松耦合。在Spring框架中&#xff0c;事件机制基于ApplicationEvent和ApplicationListener&#xff0c;为开发者提供了一种简洁而强大的事件发布和监听方式。…

MySQL中的别名

使用列别名 SELECT 列名 AS 列别名 FROM 表名 WHERE 条件; 示例&#xff1a;查询employees表将雇员last_name列定义别名为name。 select last_name as name from employees; select last_name name from employees; 使用表别名 SELECT 表别名.列名 FROM 表名 as 表别名 WH…

卡尔曼滤波中Q和R与噪声的关系

卡尔曼滤波 一种用于估计系统状态的递归滤波器&#xff0c;通过融合传感器测量和系统模型&#xff0c;提供系统状态的最优估计。 Q和R是什么 在卡尔曼滤波中&#xff0c;Q和R分别表示过程噪声和测量噪声的协方差矩阵。 Q Q Q矩阵&#xff08;过程噪声协方差矩阵&#xff09;…

【C/C++】程序的构建(编译)过程概述

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、预处理&#xff08;Preprocessi…

RK3568 android11 usb摄像头预览分辨率添加多分辨率---解除1080p限制

一&#xff0c;描述 UVC&#xff08;USB Video Class&#xff09;是一种 USB 设备类标准&#xff0c;允许通过 USB 连接的视频设备&#xff08;如摄像头、网络摄像头和其他视频捕捉设备&#xff09;与计算机或其他主机设备进行通信。UVC 使得视频设备的使用变得更加简单和通用…