C++进阶——继承
C++进阶——继承
1.继承的概念及定义
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
面向对象三大特性:封装、继承、多态。
概念:
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特
性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,
体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
这样理解:我们现在有着不同的身份,比如老师、学生、警察、医生、近战法师等等。但是我们还有最基础的特性就是我们是个人,所以我们在群里中有不同点,但是也有很多作为人的共同点,因此这样定义对象的话,每次定义一个职业的话,都得把作为人的特性加进去,虽然可以复制粘贴,可是还是好麻烦,有没有一个方法让大家作为人的的共性都设计成一个类,每个人都可以添加一下呢?那就是继承。
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{
protected:int _stuid; // 学号
};
class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}
继承的定义
上面我们看到Person就是父类,也称作基类。Student就是子类,也称作派生类。
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和
Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。
调用Print可以看到成员函数的复用(student、teacher都没写print函数可是可以调用print,是因为调用了
基类的print)。
继承关系和访问限定符
解释一下:
** 访问限定符(基类中的访问限定符) **是限定基类中的各个元素的是否可以被访问,继承方式则是派生类(子类)接受基类把他的元素继承给子类的方式。
公式:小小取小(访问限定符和继承方式选择权限最小的作为继承下来的元素的权限类型)
总结:
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是
被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。 - 基类private成员在派生类中是不能被访问,**如果基类成员不想在类外直接被访问,但需要在派生类中能
访问,就定义为protected。**可以看出保护成员限定符是因继承才出现的。 - 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类
的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。 - 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的
写出继承方式。 - 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用
protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中
扩展维护性不强。
基类和派生类对象赋值转换
1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切
割。寓意把派生类中父类那部分切来赋值过去。切片是一种编译器自带的方式,不需要现在特别关注。
2.基类对象不能赋值给派生类对象。你长大了,要赡养父母,所以你可以给你父母你的东西,但是你不能让他们再养你了(啃老)。
3.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才
是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来
进行识别后进行安全转换。(ps:这个我们以后会讲,这里了解一下)
切片示意图
class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};
class Student : public Person
{
public :int _No ; // 学号
};
void Test ()
{
Student son;// 1.子类对象可以赋值给父类对象/指针/引用Person papa = son;Person* pp = &son;Person& rp = son;//2.基类对象不能赋值给派生类对象,下一行会报错运行时注释掉。son = papa;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针。pp = &son;Student * ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &papa;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;}
越界报错
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定
义。(在子类成员函数中,可以使用 基类::基类成员 显示访问) - 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
例子1:
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :string _name = "小李子"; // 姓名int _num = 111; // 身份证号
};
class Student : public Person
{
public:void Print(){cout<<" 姓名:"<<_name<< endl;cout<<" 身份证号:"<<Person::_num<< endl;cout<<" 学号:"<<_num<<endl;}
protected:int _num = 999; // 学号
};
void Test()
{Student s1;s1.Print();
};
例子2:
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "func(int i)->" <<i<<endl;}
};
void Test()
{B b;b.fun(10);
};
只要函数名相同就会构成隐藏。
派生类的默认成员函数
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个
成员函数是如何生成的呢?
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
数,则必须在派生类构造函数的初始化列表阶段显示调用。 - 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类
对象先清理派生类成员再清理基类成员的顺序。 - 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
例子代码:
class Person
{
public :Person(const char* name = "peter"): _name(name ){cout<<"Person()" <<endl;}Person(const Person& p): _name(p._name){cout<<"Person(const Person& p)" <<endl;}Person& operator=(const Person& p ){cout<<"Person operator=(const Person& p)"<< endl;if (this != &p)_name = p ._name;return *this ;}~Person(){cout<<"~Person()" <<endl;}
protected :string _name ; // 姓名
};
class Student : public Person
{
public :Student(const char* name, int num): Person(name ), _num(num ){cout<<"Student()" <<endl;}Student(const Student& s): Person(s), _num(s ._num){cout<<"Student(const Student& s)" <<endl ;}Student& operator = (const Student& s ){cout<<"Student& operator= (const Student& s)"<< endl;if (this != &s){Person::operator =(s);_num = s ._num;}return *this ;} ~Student(){cout<<"~Student()" <<endl;}
protected :int _num ; //学号
};
void Test ()
{Student s1 ("jack", 18);Student s2 (s1);Student s3 ("rose", 17);s1 = s3 ;
}
继承与友元
**友元关系不能继承,**也就是说基类友元不能访问子类私有和保护成员
我附庸的附庸不是我的附庸。
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;
}
void main()
{Person p;Student s;Display(p, s);
}
这里s就无法打印。
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一
个static成员实例 。
class Person
{
public :Person () {++ _count ;}
protected :string _name ; // 姓名
public :static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :string _seminarCourse ; // 研究科目
};
void TestPerson()
{Student s1 ;Student s2 ;Student s3 ;Graduate s4 ;cout <<" 人数 :"<< Person ::_count << endl;Student ::_count = 0;cout <<" 人数 :"<< Person ::_count << endl;
}
复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant
的对象中Person成员会有两份。
这就产生了没有必要的冗余。
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 ; // 主修课程
};
void Test ()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a ;a._name = "peter";// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
**虚拟继承可以解决菱形继承的二义性和数据冗余的问题。**如上面的继承关系,在Student和Teacher的继承
Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
class Person
{
public :string _name ; // 姓名
};
class Student : virtual public Person
{
protected :int _num ; //学号
};
class Teacher : virtual public Person
{
protected :int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :string _majorCourse ; // 主修课程
};
void Test ()
{Assistant a ;a._name = "peter";
}
虚拟继承解决数据冗余和二义性的原理
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型。
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
下图是菱形继承的内存对象成员模型:这里可以看到数据冗余
虚拟继承之后会再子类中产生一个地址(虚表的地址)。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的父类多余继承的内容。
下面是上面的Person关系菱形虚拟继承的原理解释:
继承的总结和反思
- **很多人说C++语法复杂,其实多继承就是一个体现。**有了多继承,就存在菱形继承,有了菱形继承就有
菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在
复杂度及性能上都有问题。 - 多继承可以认为是C++的缺陷之一,很多后来的面向对象语言都没有多继承,如Java。
- 继承和组合
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承 。
继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用
(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。
继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关
系很强,耦合度高。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对
象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,
耦合度低。优先使用对象组合有助于你保持每个类被封装。
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适
合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就
用组合。
说明一下白盒测试比黑盒测试要求严格很多。还有程序中对象的建立最好都是好内聚低耦合的。
相关文章:

C++进阶——继承
C进阶——继承 1.继承的概念及定义 面向对象三大特性:封装、继承、多态。 概念: 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,这…...

数据结构---单链表
专栏:数据结构 个人主页:HaiFan. 专栏简介:从零开始,数据结构!! 单链表前言顺序表的缺陷链表的概念以及结构链表接口实现打印链表中的元素SLTPrintphead->next!NULL和phead!NULL的区别开辟空间SLTNewNod…...

redis数据结构的底层实现
文章目录一.引言二.redis的特点三.Redis的数据结构a.字符串b.hashc.listd.sete.zset(有序集合)一.引言 redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value的NoSQL数据库。 通常使用redis作为缓存中间件来降低数据库的压力,除此…...

【JavaSE】复习(进阶)
文章目录1.final关键字2.常量3.抽象类3.1概括3.2 抽象方法4. 接口4.1 接口在开发中的作用4.2类型和类型之间的关系4.3抽象类和接口的区别5.包机制和import5.1 包机制5.2 import6.访问控制权限7.Object7.1 toString()7.2 equals()7.3 String类重写了toString和equals8.内部类8.1…...

Java 主流日志工具库
日志系统 java.util.logging (JUL) JDK1.4 开始,通过 java.util.logging 提供日志功能。虽然是官方自带的log lib,JUL的使用确不广泛。 JUL从JDK1.4 才开始加入(2002年),当时各种第三方log lib已经被广泛使用了JUL早期存在性能问题&#x…...

产品经理有必要考个 PMP吗?(含PMP资料)
现在基本上做产品的都有一个PMP证件,从结果导向来说,不对口不会有这么大范围的人来考,但是需要因地制宜,在公司内部里,标准程序并不流畅,产品和项目并不规范,关系错综复杂。 而产品经理的职能又…...

什么是原型、原型链?原型和原型链的作用
1、ES6之前,继承都用构造函数来实现;对象的继承,先申明一个对象,里面添加实例成员<!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><script…...

条件期望4
条件期望例题----快排算法的分析 快速排序算法的递归定义如下: 有n个数(n≥2n\geq 2n≥2), 一开始随机选取一个数xix_ixi, 并将xix_ixi和其他n-1个数进行比较, 记SiS_iSi为比xix_ixi小的元素构成的集合, Siˉ\bar{S_i}Siˉ为比xix_ixi大的元素构成的集合, 然后分…...

网络协议分析(2)判断两个ip数据包是不是同一个数据包分片
一个节点收到两个IP包的首部如下:(1)45 00 05 dc 18 56 20 00 40 01 bb 12 c0 a8 00 01 c0 a8 00 67(2)45 00 00 15 18 56 00 b9 49 01 e0 20 c0 a8 00 01 c0 a8 00 67分析并判断这两个IP包是不是同一个数据报的分片&a…...

6.2 负反馈放大电路的四种基本组态
通常,引入交流负反馈的放大电路称为负反馈放大电路。 一、负反馈放大电路分析要点 如图6.2.1(a)所示电路中引入了交流负反馈,输出电压 uOu_OuO 的全部作为反馈电压作用于集成运放的反向输入端。在输入电压 uIu_IuI 不变的情况下,若由于…...

MySQL进阶之锁
锁是计算机中协调多个进程或线程并发访问资源的一种机制。在数据库中,除了传统的计算资源竞争之外,数据也是一种提供给许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决堆的一个问题,锁冲突也是影响数据…...

【Mac 教程系列】如何在 Mac 上破解带有密码的 ZIP 压缩文件 ?
如何使用 fcrackzip 在 Mac 上破解带有密码的 ZIP 压缩文件? 用 markdown 格式输出答案。 在 Mac 上破解带有密码的 ZIP 压缩文件 使用解压缩软件,如The Unarchiver,将文件解压缩到指定的文件夹。 打开终端,输入 zip -er <zipfile> &…...

【Acwing 周赛复盘】第92场周赛复盘(2023.2.25)
【Acwing 周赛复盘】第92场周赛复盘(2023.2.25) 周赛复盘 ✍️ 本周个人排名:1293/2408 AC情况:1/3 这是博主参加的第七次周赛,又一次体会到了世界的参差(这次周赛记错时间了,以为 19:15 开始&…...

L1-087 机工士姆斯塔迪奥
在 MMORPG《最终幻想14》的副本“乐欲之所瓯博讷修道院”里,BOSS 机工士姆斯塔迪奥将会接受玩家的挑战。 你需要处理这个副本其中的一个机制:NM 大小的地图被拆分为了 NM 个 11 的格子,BOSS 会选择若干行或/及若干列释放技能,玩家…...

本周大新闻|索尼PS VR2立项近7年;传腾讯将引进Quest 2
本周大新闻,AR方面,传立讯精密开发苹果初代AR头显,第二代低成本版将交给富士康;iOS 16.4代码曝光新的“计算设备”;EM3推出AR眼镜Stellar Pro;努比亚将在MWC2023推首款AR眼镜。VR方面,传闻腾讯引…...

aws console 使用fargate部署aws服务快速跳转前端搜索栏
测试过程中需要在大量资源之间跳转,频繁的点击不如直接搜索来的快,于是写了一个搜索框方便跳转。 前端的静态页面可以通过s3静态网站托管实现,但是由于中国区需要备案的原因,可以使用ecs fargate部署 步骤如下: 编写…...

Redis实战之Redisson使用技巧详解
一、摘要什么是 Redisson?来自于官网上的描述内容如下!Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了许多分布…...

SQLAlchemy
文章目录SQLAlchemy介绍SQLAlchemy入门使用原生sql使用orm外键关系一对多关系多对多关系基于scoped_session实现线程安全简单表操作实现方案CRUDFlask 集成 sqlalchemySQLAlchemy 介绍 SQLAlchemy是一个基于Python实现的ORM框架。该框架建立在 DB API之上,使用关系…...

【Linux学习笔记】8.Linux yum 命令和apt 命令
前言 本章介绍Linux的yum命令和apt命令。 Linux yum 命令 yum( Yellow dog Updater, Modified)是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器。 基于 RPM 包管理,能够从指定的服务器自动下载 RPM 包并且安装…...

windows服务器实用(4)——使用IIS部署网站
windows服务器实用——IIS部署网站 如果把windows服务器作为web服务器使用,那么在这个服务器上部署网站是必须要做的事。在windows服务器上,我们一般使用IIS部署。 假设此时前端给你一个已经完成的网站让你部署在服务器上,别人可以在浏览器…...

Random(二)什么是伪共享?@sun.misc.Contended注解
目录1.背景简介2.伪共享问题3.问题解决4.JDK使用示例1.背景简介 我们知道,CPU 是不能直接访问内存的,数据都是从高速缓存中加载到寄存器的,高速缓存又有 L1,L2,L3 等层级。在这里,我们先简化这些复杂的层级…...

Linux解压压缩
打包tar首先我们得提一下专门用于打包文件的命令——tartar用于备份文件,打包多个文件或者目录,也可以用于还原被打包的文件假设打包目录test下的文件 tar -cvf test.tar ./test 假设打包目录test下的文件,并用gzip命令将包压缩 tar -zcvf test.tar ./te…...

JavaSe第3次笔记
1.String str "hello";字符串类型。 2.两个字符串类型相加意思是拼接,类似于c语言里面的strcat函数。 3.整型变成字符串类型: int a 10; String str String. valueOf(a); 4.当字符串和其他类型进行相加的时候,结果就是字符串。(不完全…...

非人工智能专业怎样从零开始学人工智能?
人工智能(Artificial Intelligence,AI)是指让机器具有类似人类智能的能力,包括感知、理解、推理、学习、规划、决策、创造等多个方面。人工智能研究涉及到计算机科学、数学、物理学、心理学、哲学等多个领域,旨在模拟和…...

MyBatis之增、删、查、改
目录 前言 一、配置MyBatis开发环境 1.1 创建数据库和表 1.2 添加框架支持 1.3 创建目录结构 1.4 配置数据库连接 1.5 配置MyBatis中的XML文件路径 二、添加业务代码 2.1 查询数据库操作 2.1.1 添加实体类 2.1.2 添加mapper接口 2.1.3 在xml中实现mapper接口 2.1.…...

死磕Spring,什么是SPI机制,对SpringBoot自动装配有什么帮助
文章目录如果没时间看的话,在这里直接看总结一、Java SPI的概念和术语二、看看Java SPI是如何诞生的三、Java SPI应该如何应用四、从0开始,手撸一个SPI的应用实例五、SpringBoot自动装配六、Spring SPI机制与Spring Factories机制做对比七、这里是给我自…...

因果推断10--一种大规模预算约束因果森林算法(LBCF)
论文:A large Budget-Constrained Causal Forest Algorithm 论文:http://export.arxiv.org/pdf/2201.12585v2.pdf 目录 0 摘要 1 介绍 2 问题的制定 3策略评价 4 方法 4.1现有方法的局限性。 4.2提出的LBCF算法 5验证 5.1合成数据 5.2离线生…...

Linux基础命令-df显示磁盘的使用情况
文章目录 文章目录 df 命令介绍 语法格式 基本参数 参考实例 1)以人类可读形式显示磁盘空间的使用情况 2)显示磁盘的inode信息 3)显示磁盘和文件系统类型 4)指定显示文件系统 5)显示所有磁盘空间中的内容 …...

如何使用goquery进行HTML解析以及它的源码分析和实现原理
目录 goquery 是什么 goquery 能用来干什么 goquery quick start 玩转goquery.Find() 查找多个标签 Id 选择器 Class 选择器 属性选择器 子节点选择器 内容过滤器 goquery 源码分析 图解源码 总结 goquery 简介 goquery是一款基于Go语言的HTML解析库,…...

【Java 数组和集合 区别及使用案例】
Java中数组和集合都是用来存储一组数据的容器,但是在实际使用中,它们有一些区别和不同的使用场景。 数组 vs 集合:存储方式 数组是一个固定长度的容器,它的长度一旦被初始化之后,就无法再改变了。而集合是一个动态长…...