【C++进阶】深入了解继承机制

目录

前言

1. 继承的概念

2. 继承的定义

 3. 继承中基类与派生类的赋值转换

 4. 继承中的作用域

 5. 派生类的默认成员函数

 6. 继承与友元、静态成员

 7. 多继承与菱形继承

7.1 如何解决


前言

         继承是面向对象编程中的一个重要概念,也是面向对象编程语言中普遍存在的特性,本文我将会深入的向大家介绍C++的继承以及继承中菱形继承的问题;

在这里插入图片描述

1. 继承的概念

        继承是面向对象的程序设计,使代码可以复用的最重要的手段,在使用时我们可以保持原有类特性的基础上进行扩展,增加功能,产生新的类,我们称之为派生类;继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

         之前我们在实现函数中,如果存在多个函数中重复相同的操作,我们就会把这个重复操作单独写成一个函数,以便于不同接口的调用;在类当中也存在这样的问题,两个或者多个类同时拥有相同属性,比如:

class Student 
{
public://...
protected:string _name;// 姓名string _gender;// 性别string _id;	 // 身份号int age;	 // 年龄int _stuid;  // 学号
};class Teacher
{
public://...
protected:string _name;string _gender;string _id;int age;int _jobid; // 工号
};

他们都拥有一些相同的属性,这时我们就可以把这些属性提取出来,写成一个类Person,用的时候只需继承Person类即可;

2. 继承的定义

 继承的定义规则:

 前边我们在学习类的时候提到,类有三种访问限定符:

public、protected、private;

 它的继承方式也是这三种,继承方式不同,派生类使用基类时的权限也不同:

 继承基类成员访问方式的变化

 小tips:

 这张表我们可以分为两部分:

        基类的private成员为一类,它在派生类中不可见(不可见不代表没有继承下来),派生类无法使用(类里边和类外边都无法使用)

         基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private

         在继承部分才能体现出private和protected的区别

 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected;

         继承一个类时没有写继承方式,它们的默认继承方式会因为声明有些不同的,使用的是class,子类继承时默认是private,使用struct时默认的继承方式是public

class Person
{
public :void Print (){cout<<_name <<endl;}
protected :string _name ; // 姓名string _gender;// 性别string _id;	 // 身份号
private :int _age ; // 年龄
};
//class Student : Person
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected :int _stunum ; // 学号
};

 3. 继承中基类与派生类的赋值转换

         在继承体系中,派生类对象 可以赋值给 基类的对象,包括派生类的指针,引用都可以赋值给基类;这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去;

Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//他们中间不会产生临时对象

 父子继承是一种 is-a 的关系,子类是一个特殊的父类,子类包含一个父类,所以可以通过切片的方式将派生类对象赋值给父类但父类对象不能赋值给子类

 4. 继承中的作用域

 .      在继承体系中基类和派生类都有独立的作用域;子类创建的对象可以在类外部调用父类的public成员函数;

        当子类和父类中有相同的成员时,子类对象调用时,优先调用自己的成员;子类成员会屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义;

class Person
{
public:void fun(){cout << "父类" << endl;}
protected:string _name = "小李子"; // 姓名int _num = 111; 	   // 身份证号
};class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;// 当子类和父类中有两个相同变量时,可以通过指定类域进行访问cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}void fun(){cout << "子类" << endl;}
protected:int _num = 999; // 学号
};
int main()
{Student s;s.Print();//默认调用的是子类的成员//可以通过指定类域的方式进行调用s.Person::fun();return 0;
}

但是我们也可以通过指定类域的方法去调用父类的成员; 继承中,同名的成员函数,函数名相同就构成隐藏,不管参数和返回值

注意:

        在实际中应用中,继承体系里面最好不要定义同名的成员!

 5. 派生类的默认成员函数

 现在需要结合前边类的默认成员函数,这部分相对较为复杂;

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:protected:int _id;
};int main()
{	Student s1;return 0;
}

 我们实现一个父类,Student继承Person,Student什么都不实现,编译器会默认生成的构造函数和析构函数;默认的构造函数和析构函数会去调父类的构造和析构;

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员;
  • 派生类对象析构先调用派生类析构再调基类的析构(保证派生类对象先清理派生类成员再清理基类成员的顺序);
  • 派生类对象初始化先调用基类构造再调派生类构造
     

 要实现子类的构造函数,来对子类对象进行初始化:

Student(const char* name, int id):_id(id), Person(name) // _name(name)不可以
{cout << "Student(const char* name, int id)" << endl;
}Student s1("张三", 18);
  • 对子类对象进行初始化,同时也需要传参数对父类进行初始化
  • 对父类进行初始化时需要注意,子类是父类的衍生类,
  • 在对父类部分进行初始化时,要把父类看作一个单独的类进行初始化(对父类整体进行初始化)
  • 不可以单独对父类成员进行初始化

拷贝构造:

Student(const Student& s):Person(s), _id(s._id)
{cout << "Student(const Student& s)" << endl;
}Student s2(s1);

        使用s1进行初始化,对父类进行初始化时可以直接使用子类进行赋值,衍生类在向基类赋值时会有切片(切割),基类会截取对基类部分初始化的数据;这里也体现出了切片的意义;

 赋值重载:

Student& operator=(const Student& s)
{if (&s != this){// 注意这里需要指定类域,不然会构成隐藏,一直调用子类的operator=Person::operator=(s);_id = s._id;}cout << "Student& operator=(const Student& s)" << endl;return *this;
}Student s3("李四", 19);
s1 = s3;

 派生类的operator=必须要调用基类的operator=完成基类的复制;

 派生类的析构:

~Student()
{//Person::~Person();//父类的析构不可以显示的调用比如:~Person();cout << "~Student()" << endl;
}

         这是因为父子类的析构函数构成隐藏,由于多态的原因,析构函数统一会被处理成destructor,如果想要调用就必须指定类域;但是调用后又会很奇怪,每个派生类对象调用了两次基类的析构函数,父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构,保证析构先子后父(构造函数先父后子),我们调用的父类析构函数是多余的;(如果子类显式调用就会造成析构先父后子,存在风险)

总结就是派生类中有一个基类,这个基类需要把它当成一个单独的类来处理;

 6. 继承与友元、静态成员

 继承与友元只有一个需要注意的点:

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

 继承与静态成员:

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

 7. 多继承与菱形继承

 前边我们玩的都是单继承,单继承:一个子类只有一个直接父类时称这个继承关系为单继

 现在我们来聊一聊多继承,多继承可以说是C++的一个不好的设计,它又会引发菱形继承的问题;

什么是多继承?

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

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

 菱形继承的问题:从下面的对象成员模型构造,课以看出他们的继承关系,Student和Teacher继承了Person(Student和Teacher内部都有一个Person),Postgraduate又继承了Student和Teacher,Postgraduate内部就会有两个Person;编译器到底用哪个Person?

 从这里可以看出菱形继承有数据冗余和二义性的问题。

7.1 如何解决

 C++有具体的应对措施,那就是虚拟继承(虚继承),在继承时加上virtual;

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 ()
{//使用虚继承后他们用的就是同一PersonAssistant a ;a._name = "peter";
}

 注意virtual不是随便加的,它有具体的添加位置;virtual要添加在派生类对基类的继承声明中的类名之前;

 C++的语法设计复杂,多继承就是一个体现,有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题;

8. 组合

         继承一般不会单独的使用,因为继承的使用会增加程序的耦合度,程序设计要求的是高内聚,低耦合,就是减少代码之间的关联度;C++的继承都是配合多态一起使用;介绍了继承,那就不得不提一下组合;

  • 继承是is - a的关系:每个派生类对象都是一个基类对象;
  • 组合就是has - a的关系:假设B组合了A,每个B对象中都有一个A对象;;
class A
{
protected:int data;
};class B
{
protected:double x;A _a; 
};

类之间的关系可以用继承,也可以用组合,但能用组合最好还是用组合,优先使用组合!


 总结

         继承是C++的一个重要的概念,继承几乎不会单独使用,更多的是配合多态使用,要实现多态,必须需要继承;下期我将会向大家介绍C++的多态;好了以上便是本文的全部内容,希望可以对你有所帮助,感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

主数据管理是数字化转型成功的基石——江淮汽车案例分享

汽车行业数字化转型的背景 在新冠疫情导火索的影响下&#xff0c;经济全球化政治基础逐渐动摇。作为全球最大的汽车市场&#xff0c;我国的汽车市场逐渐由增量转为存量市场。 在数字化改革大背景下&#xff0c;随着工业4.0时代的到来&#xff0c;江淮汽车集团力争实现十四五数…

BFS中的双向广搜和A-star

双向广搜 190. 字串变换 - AcWing题库 import java.util.*;public class Main{static int N 6, n 0;//规则数不超过6个&#xff0c;n表示规则数量static String[] a new String[N];//字串a&#xff0c;用于规则变换static String[] b new String[N];//字串b&#xff0c;用…

2.2 RK3399项目开发实录-使用USB线缆升级固件(物联技术666)

2.1. 前言 本文介绍了如何将主机上的固件&#xff0c;通过USB数据线烧录到 AIO-3399J 开发板的存储器中。升级时&#xff0c;需要根据主机操作系统和固件类型来选择合适的升级方式。 2.2. 准备工具 AIO-3399J 开发板 固件 主机 良好的双公头USB数据线数据线 2.3. 准备固件 …

leetcode 2.合并两个有序链表

1..题目&#xff1a;合并两个有序链表&#xff1b; 2.用例&#xff1a; 3.解题思路&#xff1a; &#xff08;1&#xff09;函数头&#xff1a;参数是两个链表&#xff1b;返回值为 链表指针 ListNode*&#xff1b; &#xff08;2&#xff09;函数体&#xff1a; 1.首先比较…

python学习笔记-内置异常

概述 Python 中的异常&#xff08;Exception&#xff09;是指在程序执行过程中遇到的错误或异常情况。当程序出现异常时&#xff0c;解释器会停止当前代码的执行&#xff0c;并试图找到匹配的异常处理器来处理异常。如果没有找到合适的异常处理器&#xff0c;程序就会终止并打…

PDF文件转换为图片

现在确实有很多线上的工具可以把pdf文件转为图片&#xff0c;比如smallpdf等等&#xff0c;都很好用。但我们有时会碰到一些敏感数据&#xff0c;或者要批量去转&#xff0c;那么需要自己写脚本来实现&#xff0c;以下脚本可以提供这个功能~ def pdf2img(pdf_dir, result_path…

搭建Facebook直播网络对IP有要求吗?

在当今数字化时代&#xff0c;Facebook直播已经成为了一种极具吸引力的社交形式&#xff0c;为个人和企业提供了与观众直接互动的机会&#xff0c;成为推广产品、分享经验、建立品牌形象的重要途径。然而&#xff0c;对于许多人来说&#xff0c;搭建一个稳定、高质量的Facebook…

【Godot4.2】菜单栏生成函数库menuDB

概述 关于Godot的手动菜单栏制作&#xff0c;我已经在之前的文章中有所描述。 但是对于一些场景&#xff0c;手动制作菜单仍然是一个比较低效的做法。所以我将MenuBar以及基于HBoxContainerMenuButton创建菜单栏写成了一个静态函数库。 利用此函数库我们可以用函数形式构造P…

前后端分离vue+php仓库管理系统 n2bm6

1.功能需求 &#xff08;1&#xff09;系统功能包括 &#xff1a;产品入出库登记、确认入出库信息、删除库内信息、借出信息登记、产品分类管理、&#xff0c;报表生成&#xff0c;事件记录&#xff0c;数据检测、数据警告。 &#xff08;2&#xff09;系统管理员功能&#xf…

基于JSP的毕业设计选题系统的设计与实现

基于JSP的毕业设计选题系统的设计与实现 (源代码论文) A. 项目简介 毕业设计选题系统就是能够使学生通过互联网完成毕业设计课题的选定&#xff0c;它采用Web方式&#xff0c;同时适用于局域网和Internet&#xff0c;它要实现审核&#xff0c;权限管理&#xff0c;邮件通知…

C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-02-26T08:38:44.171849 description&#xff1a;Ultralytics YOLOv8s-obb model trained on runs/DOT…

有适合短视频剪辑软件的吗?分享4款热门软件!

在数字时代&#xff0c;短视频已成为人们获取信息、娱乐消遣的重要形式。随着短视频行业的蓬勃发展&#xff0c;市场上涌现出众多短视频剪辑软件&#xff0c;它们功能各异&#xff0c;各具特色。本文将为您详细介绍几款热门短视频剪辑软件&#xff0c;助您轻松掌握短视频剪辑技…

BIO实战、NIO编程与直接内存、零拷贝深入辨析

BIO实战、NIO编程与直接内存、零拷贝深入辨析 长连接、短连接 长连接 socket连接后不管是否使用都会保持连接状态多用于操作频繁&#xff0c;点对点的通讯&#xff0c;避免频繁socket创建造成资源浪费&#xff0c;比如TCP 短连接 socket连接后发送完数据后就断开早期的http服…

phpldapadmin This base cannot be created with PLA

phpldapadmin This base cannot be created with PLA 1、问题描述2、问题分析3、解决方法&#xff1a;创建根节点 1、问题描述 安装phpldapadmin参考链接: https://blog.csdn.net/OceanWaves1993/article/details/136048686?spm1001.2014.3001.5501 刚安装完成phpldapadmin&…

MyCAT从入门到实战(MyCAT2注释配置)

重置配置 /* mycat:resetConfig{} */; 用户相关 创建用户 /* mycat:createUser{ "username":"user", "password":"", "ip":"127.0.0.1", "transactionType":"xa"} */; 删除用户 /* myc…

软件License授权原理

软件License授权原理 你知道License是如何防止别人破解的吗&#xff1f;本文将介绍License的生成原理&#xff0c;理解了License的授权原理你不但可以防止别人破解你的License&#xff0c;你甚至可以研究别人的License找到它们的漏洞。喜欢本文的朋友建议收藏关注&#xff0c;…

Maya笔记 设置工作目录

Maya会把素材场景等自动保存在工作目录里&#xff0c;我们可以自己定义工作目录 步骤1 创建workspace.mel文件 文件/设置项目 ——>选择一个文件夹&#xff0c;点击设置——>创建默认工作区 这一个后&#xff0c;可以在文件夹里看到.mel文件 步骤2 自动创建文件夹…

华为OD机试真题-靠谱的车-2023年OD统一考试(C卷)---Python3-开源

题目&#xff1a; 考察内容&#xff1a; 思维转化&#xff0c;进制转化&#xff0c;9进制转为10进制&#xff0c;在4的位置1&#xff0c;需要判断是否大于4 代码&#xff1a; """ 题目分析&#xff1a; 9进制转化为10进制23-25 39-50 399-500输入&#xff1a…

Qt的QThread、QRunnable和QThreadPool的使用

1.相关描述 随机生产1000个数字&#xff0c;然后进行冒泡排序与快速排序。随机生成类继承QThread类、冒泡排序使用moveToThread方法添加到一个线程中、快速排序类继承QRunnable类&#xff0c;添加到线程池中进行排序。 2.相关界面 3.相关代码 widget.cpp #include "widget…

安防视频汇聚EasyCVR视频云存储平台GB28181设备全部离线是什么原因?

众所周知&#xff0c;安防视频汇聚平台EasyCVR不仅可支持的接入协议非常多&#xff08;包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等&a…