【C++第十五章】继承

news/2024/12/23 1:15:06/

【C++第十五章】继承

定义🧐

  继承是C++面向对象编程中的一个核心概念,它允许创建一个新类(称为派生类或子类)从已有类(称为基类或父类)中继承属性和方法。

  继承的主要用途包括:

  • 代码重用:通过继承可以重用基类的实现。
  • 扩展与修改:派生类可以在继承的基础上添加新的属性和方法或修改基类的行为。
  • 实现多态:继承是实现多态(一种动态类型系统)的基础。

  子类可以含有父类的成员变量,可以调用父类成员函数,但是相反的父类无法使用子类的成员,并且友元关系无法继承。·

Pasted image 20240812143814

Pasted image 20240812144024

  它的定义格式为:

class Person
{//父类成员
};//继承格式
class Teacher : public Person
{//子类成员
};

继承方式和访问限定符🧐

  继承方式和访问限定符都有三种,public、protected以及private,其中public和private在之前就介绍过,如果是protected那么只有子类能访问,外界无法访问。子类继承方式表示继承的类是什么属性,子类继承方式不写默认为私有。

image-20240826195919793

  父类private成员在子类中无论以什么方式继承都是不可见的,其含义是私有成员还是被继承到子类中,但是在语法上限制了派生类对象不管在类里面还是类外面都无法访问。

  继承方式大多以public为主,并且不提倡使用protected和private继承,因为继承下来的成员都只能在子类的类里面使用,实际中维护性不强。

父子类赋值兼容规则🧐

  公有继承被看做相近类型,可以进行隐式类型转换

Pasted image 20240812155440

  隐式类型转换会生成临时变量,但是在公有继承下,父类与子类是is-a(你就是我)的关系,子类对象赋值给父类对象/父类指针/父类引用,我们认为是天然的,中间不产生临时对象,称为父子类赋值兼容规则(也叫切割/切片)

Pasted image 20240812155758

image-20240826200745502

父子成员同名🧐

  当父类和子类都有同一个成员时,会优先使用自己的成员,如果我们要使用父类成员可以加上父类的域。由此得知,继承中同名函数会构成隐藏,不管参数和返回值,所以尽量不要使用同名函数。

Pasted image 20240812163432

image-20240826200922235

子类的默认成员函数🧐

  我们以下面代码为例:

class Person
{
public:Person(const char* name, int age):_name(name),_age(age){cout << "Person构造函数" << endl;}Person(const Person& p):_name(p._name),_age(p._age){cout << "Person拷贝构造" << endl;}Person& operator=(const Person& p){cout << "Person赋值重载" << endl;if (&p != this){_name = p._name;_age = p._age;}return *this;}void Print(){cout << _age << " " << _name << endl;}~Person(){cout << "Person析构函数" << endl;}
protected:int _age = 18;string _name = "Mick";
};class Teacher : public Person
{
public:void Print(){cout << Person::_age << endl;}
protected:int _age = 10;int _jobid = 0;
};

  子类不写默认构造函数,那么会去调用父类的默认构造函数

Pasted image 20240812170347

  如果要显示初始化子类,且要用父类成员时,需要把父类当成完整对象,复用父类成员完成初始化。

Pasted image 20240812172742

  拷贝构造需要在子类中取到父类成员,我们对子类切片即可。

Pasted image 20240812173433

  同理,赋值重载也是这种方法。

Pasted image 20240812175802

  子类析构要特殊一点,因为多态的存在,析构函数名会被统一处理成destructor,所以父子类析构函数会构成隐藏,当我们调用父类析构时需要加上域。并且,构造时是先构造父类再构造子类,析构则是先子后父,原因在于父类先析构了,但子类依然能够访问父类,那么就会存在风险,所以要先释放子类,父类访问不了子类,则不存在该风险,编译器为了确保安全,所以只需要析构子类,父类会自动帮我们析构

Pasted image 20240812180058

  完整代码如下,可以自己调试学习:

#include<iostream>
using namespace std;class Person
{
public:Person(const char* name, int age):_name(name),_age(age){cout << "Person构造函数" << endl;}Person(const Person& p):_name(p._name),_age(p._age){cout << "Person拷贝构造" << endl;}Person& operator=(const Person& p){cout << "Person赋值重载" << endl;if (&p != this){_name = p._name;_age = p._age;}return *this;}void Print(){cout << _age << " " << _name << endl;}~Person(){cout << "Person析构函数" << endl;}
protected:int _age = 18;string _name = "Mick";
};class Teacher : public Person
{
public:Teacher(const char* name,int age,int id):Person(name,age),_age(age),_jobid(id){cout << name << " " << _age << " " << _jobid << endl;}Teacher(const Teacher& t):Person(t) //切片,_age(t._age),_jobid(t._jobid){cout << "Teacher拷贝构造" << endl;}Teacher& operator=(const Teacher& s){if (&s != this){Person::operator=(s); //这里要指定,不然会发生隐藏_jobid = s._jobid;_age = s._age;}return *this;}//由于多态原因,析构函数统一会被处理成destructor//父子类的析构函数构成隐藏//为了保证析构安全,先子后父//父类析构函数不需要显示调用,子类析构函数会自动调用父类~Teacher(){// Person::~Person();cout << "Teacher析构" << endl;}void Print(){cout << _name << " " << _age << " " << _jobid << endl;}
private:
protected:int _age = 10;int _jobid = 0;
};int main()
{Teacher t("张三",18,20023030);Teacher t1("李四", 10, 20043030);t = t1;t.Print();return 0;
}

关键字final🧐

  在C++11之前,我们想要一个类不能被继承,可以将构造函数私有化,而在C++11后,我们可以在父类加上final来禁止继承

Pasted image 20240813133557

静态成员继承🧐

  静态成员继承的是使用权,它存在静态区中,属于整个类。

Pasted image 20240813134251

多继承🧐

  多继承格式如下:

class student
{//成员
};class Teacher
{//成员
};//多继承格式
class Assistant : public student, public Teacher
{//子类成员
}

Pasted image 20240813134932

  但是在C++中可能会出现菱形继承,assistant会拥有两份person成员。

Pasted image 20240813135302

  菱形继承不仅会造成代码冗余,并且会出现二义性,编译器无法识别我们想调用哪个类的成员,必须要加上类域才能使用。

Pasted image 20240813140729

  解决方法是在冗余的继承类加上virtual(虚继承)

Pasted image 20240813141211

  虚拟类会多开一个空间存储偏移量表,当我们使用时,会有一个指针去指向偏移量表,计算偏移量然后找到A的地址,我们以下面这段代码为例:

class A
{
public:int _a;
};class B : public A
{
public:int _b;
};class C : 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;
}

  我们在内存窗口中看一下对象d的存储情况,发现了数据冗余,A出现了两次。

image-20240826212102805

  当我们加上虚继承后发现最开始存储1和2的地方变成了地址,并且将A放到了最下面,这个A同时属于B和C,而B和C存储了两个指针,指向两张表,这个两个指针叫虚基表指针,这两个表叫虚基表虚基表中存的是偏移量,可以通过偏移量找到下面的A。

image-20240826212318455

组合与继承🧐

  如下代码,B类和D类大小一样,但一个是继承一个是组合,区别在于继承权限更大,组合只能使用公有的成员,且在类外不能直接调用成员函数,从可维护性来看,组合更好,因为组合依赖关系不强,耦合度低,有助于保持每个类被封装,但从便利角度来看,继承更好用。

#include<iostream>
using namespace std;class A
{
public:void func(){}
protected:int _a;
};
class B : public A
{
public:void f(){func(); //直接调用_a = 1; //可以访问_}
protected:int _b;
};
class C
{
public:void func(){}
protected:int _c;
};
class D
{
public:void f(){_cc.func(); //间接调用_c = 1; //不可以访问}
protected:int _d;C _cc;
};
int main()
{B bb;D dd;bb.func(); //可以调用dd.func(); //无法调用return 0;
}

Pasted image 20240813185121

小试牛刀🧐

题目一:

Pasted image 20240813192026

  答案:Derive继承了base1和base2,由于是先继承base1,所以p3和p1恰好从同一地址开始,而base2是在base1之后继承的,所以p2地址不同,则选C

题目二:

Pasted image 20240813201858

  答案:D的构造函数会先走初始化列表,而继承顺序决定声明顺序,并且这里是虚继承(如果没有虚继承A会调用两次,A自己本身不需要走初始化列表),A只调用一次,所以选A

总结🧐

  1. 尽量不要设计多继承,且一定不要设计出菱形继承,这样会在复杂度和性能上存在问题。
  2. 多继承可以说是C++设计缺陷之一,java中就没有多继承。
  3. public继承是is-a的关系,组合是一种has-a的关系。
  4. 优先使用对象组合,而不是类继承。
  5. 继承允许你根据父类的实现来定义子类的实现,这种通过生成子类的复用通常被称为白箱复用。“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见,继承一定程度破坏了父类的封装,父类的改变,对子类有很大的影响。父子类依赖关系很强,耦合度高
  6. 对象组合是类继承的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称为黑箱复用,因为内部细节不可见。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低,能够保证每个类的封装。
  7. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

结尾👍

  以上便是继承的全部内容,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹


http://www.ppmy.cn/news/1517561.html

相关文章

U盘读不出来怎么办

目录 一、检查物理连接 1. 重新插拔U盘 2. 检查U盘外观 二、软件设置检查 1. 取消隐藏U盘 2. 更新或重新安装U盘驱动 3. 检查磁盘管理 三、文件系统修复 1. 格式化U盘 2. 使用命令提示符修复 四、病毒扫描 五、其他注意事项 一、检查物理连接 1. 重新插拔U盘 最简单也…

RTC相关

RTC唤醒 &#xff08;Real Time Clock) sudo rtcwake -m [mode] -s [seconds]-m 选项指定进入的电源管理模式&#xff0c;可以是&#xff1a; standby&#xff1a;进入待机模式 freeze&#xff1a;冻结模式 mem&#xff1a;挂起到内存 disk&#xff1a;挂起到磁盘 off&#xf…

AI产品经理学习路线【2024最新】,从零基础到精通,非常详细收藏我这一篇就够了

成为一名优秀的AI产品经理不仅需要掌握相关的技术知识&#xff0c;还需要具备良好的产品思维、市场洞察力以及跨部门沟通协调能力。下面是一个详细的AI产品经理学习路线&#xff0c;旨在帮助有志于从事该职业的人士快速成长。 AI产品经理的学习路线 第一阶段&#xff1a;基础知…

一篇讲完自动化基础-Python【万字详细讲解】

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这篇文章主要学习Python的语法&#xff0c;为后续的自动化打基础 Python requests 接口自动化 Python selenium web 自动化 Python appium移动端自动化(手机 app) 这篇文章分六个阶段百分比进行划分&#xff0c;到时…

基于伏图的汽车发动机曲轴模态仿真APP应用介绍

汽车发动机是为汽车提供动力的装置&#xff0c;是汽车的心脏&#xff0c;决定着汽车的动力性、经济性、稳定性、舒适性和环保性。曲轴是发动机中最重要、承载最复杂的零件之一&#xff0c;其强度和振动特性都会影响到整机的工作性能。 汽车发动机剖面图 曲轴在运转时&#xff…

您应该让 ChatGPT 控制您的浏览器吗?

本文: 介绍授予大型语言模型 (LLM) 对 Web 浏览器的控制权的安全风险,重点关注提示注入漏洞。 通过两种场景演示了使用 Taxy AI(一种代表性概念验证浏览器代理)的利用,攻击者设法劫持代理并 (1) 从用户邮箱中窃取机密信息,(2) 强制合并 GitHub 存储库上的恶意拉取请求。 …

USB:USB历史以及概况

USB:USB历史 USB历史USB概况 USB历史 USB 是一种行业标准&#xff0c;用于将电子外围设备&#xff08;例如&#xff1a;键盘、鼠标、调制解调器和硬盘驱动器&#xff09;连接到计算机上&#xff0c;它代替了尺寸大且速度慢的连接&#xff08;例如&#xff1a;串行和并行端口&a…

【精选】基于数据可视化的智慧社区内网平台(程序员阿龙出品精品)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…