从零开始的c++之旅——继承

1. 继承

1.继承概念及定义

        继承是面向对象编程的三大特点之一,它使得我们可以在原有类特性的基础之上,增加方法
        和属性,这样产生的新的类,称为派生类。

        继承 呈现了⾯向对象程序设计的层次结构,以前我们接触的函数层次的 复⽤,继承是类设计
        层次的复⽤。

        例如我们在实现老师的类teacher和学生的类student时,他们都有姓名/地址/ 电话/年龄等成员
        变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们 也有⼀些
        不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣
        的独有成员函数是学习,⽼师的独有成员函数是授课。

class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 学习 void study(){// ...}
protected:string _name = "peter"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 int _stuid; // 学号 
};class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 授课 void teaching(){//...}
protected:string _name = "张三"; // 姓名 int _age = 18; // 年龄 string _address; // 地址 string _tel; // 电话 string _title; // 职称 
};

        我们可以将他们两个类中冗余的部分提取出来,实现一个新的类Person,就可以复用这些成
        员,不需要重新定义,省时省力。

class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 
};
class Student : public Person
{
public:// 学习 void study(){cout << Person::_name << endl;}
protected:int _stuid;// 学号string _name = "李四";
};
class Teacher : public Person
{
public:// 授课 void teaching(){//...}
protected:string title; // 职称 string _name = "王五";};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}

1.2 继承定义

1.2.1 格式

如下图所示,Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。

1.2.2 继承方式及其特点

         1.如果我们不想让基类的对象被派生类访问,就将基类中的对象定义为private,这样无论是
            哪种继承方式,无论是在类内部还是外部都无法访问。

         2.如果想让基类的对象继承之后可以在派生类中被访问,但是不能在类外被访问,就定义成
            protect,这也是protect和private的区别,所以如果不涉及继承他们两个的作用是一致的

         3.有上表格我们可以总结出继承的规律:
            基类的私有成员在派⽣类都是不可⻅。
            基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),
            public > protected > private。

         4.继承方式最好显示写出来,即使我们知道class默认private,struct默认public。

         5. 当然虽然c++中的继承方式相对复杂,但是我们实际当中基本都是使用public继承。
             也不提倡使用protetced/private继承,因为这两者继承下来的成员都只能在派⽣类的类⾥⾯
             使⽤,实 际中扩展维护性不强。

1.3 继承类模板

        在继承类模板的时候需要注意,当我们的基类是一个类模板的时候,我们使用其中的方法需
        要指定类域,不然编译器找不到对应的方法,因为模板的实例化是按需实例化,只有用了对
        饮的方法才对去实例化对应的方法,

	template<class T>class stack : public std::vector<T>{public:void push(const T& x){ vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};

2. 基类和派生类之间的转换

         public继承的 派生类对象 可以赋值给 基类的指针/基类的引用 。我们形象的将其称为切片。
        意思就是将派生类中的基类那部分切出来,基类指针只会指向派生类对象中基类有的那部分
        对象,下面为示例

class Person
{
protected :string _name; // 姓名 string _sex; // 性别 int _age; // 年龄 
};
class Student : public Person
{
public :int _No ; // 学号 
};
int main()
{Student sobj ;// 1.派⽣类对象可以赋值给基类的指针/引⽤ Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错 sobj = pobj;return 0;

 需要注意的是 Person& rp = sobj 这条语句在执行过程中并没有产生临时对象,而是直接赋值给了rp。

3. 继承中的作用域

3.1 隐藏规则:

        1. 在继承体系中基类和派生类都有独自的作用域。
        2. 若基类和派生类有同名成员,则派生类成员将会屏蔽基类对同名成员的直接访问,这种现
            象叫做隐藏。
        3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
        4. 住哟i在实际继承体系中最好不要定义同名成员。

3.2 注意事项

        函数重载和隐藏都要求同名函数,但是函数重载的行为是要求同一作用域,而隐藏的要求是
        两个类为派生类和基类中有函数同名

        如果我们要在派生类当中调用基类被隐藏的同名函数,就需要指定类域。

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

4个默认成员函数

        类有6个默认成员函数,我们主要讨论其中较为重要的4个。

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那部分成员。如果基类没有默认的            构造函数,则必须在派生类构造函数初始化列表显示调用

	Student(const char* name, int num ,const string& address): Person(name)//父类成员调用父类的构造函数,没有就在子类初始化列表显示写, _num(num), _address(address){cout << "Student()" << endl;}

    2. 派生类的拷贝函数必须调用基类的拷贝构造完成棋类的拷贝初始化。

	Student(const Student& s): Person(s)//把基类看作一个整体, _num(s._num){cout << "Student(const Student& s)" << endl;}

    3. 派生类的operator=必须调用基类的operator完成基类的复制。需要注意的是派生类的
        operator=会隐藏基类的operator等于,使用显示调用基类operrtor=需要指定类域

	Student& operator = (const Student& s){if (this != &s){// 构成隐藏,所以需要显⽰调⽤ Person::operator =(s);//将基类对象看作整体_num = s._num;}return *this;}

     4. 派生类的析构函数会在被调用后自动取调用基类的析构函数,因为这样才能保证派 ⽣类对象
         先清理派⽣类成员再清理基类成员的顺序。因此我们显示写派生类的析构函数时候需要注意
         不用显示写基类的析构函数

	~Student(){//不用写基类的析构函数}

     5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。

     6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(后续的多态章节
         会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数
         不加 virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。

要点总结


         不写,编译器默认生成的行为是什么?
         默认生成不符合我们需求,自己写,得怎么写?
 
         特点:子类中继承下来的父类成员当做一个整体对象


         构造:
                 默认:子类成员 内置类型(有缺省值就用,没有不确定)和自定义类型(默认构造) + 父类
                 成员(必须调用父类默认构造) 


         拷贝构造:
                子类成员 内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类
                拷贝构造)

         赋值重载:
                类似拷贝构造

         析构:
                子类成员 内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
                自己实现的话,注意不需要显示调用父类析构,子类析构函数结束后,会自动调用父类
                析构

4. 继承和友元

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

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;
}

5. 继承和静态成员

基类定义了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;
}

6. 多继承及其菱形继承问题

6.1 继承模型

        单继承: 一个派生类只有个一个直接的基类

        多继承: ⼀个派⽣类有两个或以上直接基类,多继承对象在内存中的模型 是,先继承的基类
                        在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。

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

从上图得出,菱形继承数据冗余和二义性问题,在Assistant的对象中Person成员会有两份。⽀持多继承就 ⼀定会有菱形继承,所以实践中我们也是不建议 设计出菱形继承这样的模型的。

                        ​​​​​​​        ​​​​​​​        

6.1 虚继承

        在具有二义性的类前面加上关键字virtual,可以实现虚继承,解决菱形继承的问题,但是对于
        计算机来说会造成多余的新能损失

class Person
{
public:string _name; // 姓名 /*int _tel;int _age;string _gender;string _address;*/// ...
};// 使⽤虚继承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; // 主修课程 
};int main()
{// 使⽤虚继承,可以解决数据冗余和⼆义性 Assistant a;a._name = "peter";return 0;
}

        很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形
        继承就有 菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱
        形继承。多继承可 以认为是C++的缺陷之⼀。

        我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤
        还是底层 都会复杂很多。

        虽然菱形继承会造成一些不必要的麻烦,但是一些底层的实现还是需要用上的。但对于小萌
        新来说还是只可远观不可亵玩

8. 继承和组合 

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

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

        继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩
        箱复⽤。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅。继承
        ⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依 赖
        关系很强,耦合度⾼。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学习】软件测试中V模型、W模型、螺旋模型三者介绍

在软件工程的星辰大海之中&#xff0c;存在着三种独特的航路图&#xff1a;V模型、W模型以及螺旋模型。它们分别以各自的方式描绘了软件开发与测试的不同旅程。 首先映入眼帘的是V模型——一个以垂直线条贯穿始终的简洁图形。这个模型如同一座倒立的“V”字形山峰&#xff0c;…

SpringMVC笔记 一万字

此笔记来自于B站尚硅谷 文章目录 一、SpringMVC 简介1、什么是MVC2、什么是SpringMVC3、SpringMVC的特点 二、HelloWorld1、开发环境2、创建maven工程a>添加web模块b>打包方式&#xff1a;warc>引入依赖 3、配置web.xmla>默认配置方式b>扩展配置方式 4、创建请求…

【快速上手】pyspark 集群环境下的搭建(Standalone模式)

目录 前言 &#xff1a; 一、spark运行的五种模式 二、 安装步骤 安装前准备 1.第一步&#xff1a;安装python 2.第二步&#xff1a;在bigdata01上安装spark 3.第三步&#xff1a;同步bigdata01中的spark到bigdata02和03上 三、集群启动/关闭 四、打开监控界面验证 前…

三周精通FastAPI:31 使用 StaticFiles从目录中自动提供静态文件

官方文档&#xff1a;静态文件 - FastAPI 静态文件 您可以使用 StaticFiles从目录中自动提供静态文件。 使用StaticFiles 导入StaticFiles。"挂载"(Mount) 一个 StaticFiles() 实例到一个指定路径。 from fastapi import FastAPI from fastapi.staticfiles impo…

【双目视觉标定】——1原理与实践

0 前言 双目视觉定位是目前机器&#xff08;机器人&#xff09;等领域中使用得非常广泛的视觉定位技术&#xff0c;双目视觉是模拟人的视觉系统利用两个不同位置的摄像头的视差来确定物体的位置。由于有需要采集两个摄像头的图像共同参与计算&#xff0c;所以双目相机装配要求…

【最佳牛围栏——二分】

题目 思路 扩大数据&#xff0c;避免精度问题&#xff0c;拉到整数域解决不用枚举前缀和的 l 和 r&#xff0c;改为求可能 l 的最小值&#xff0c;线性做法不用记录长度来求平均值&#xff0c;改为用平均值处理数据&#xff08;这是第二条的前提&#xff09;&#xff0c;直接通…

[java][高级]MyBatisPlus

一、MyBatisPlus简介 1. 入门案例 问题导入 MyBatisPlus环境搭建的步骤&#xff1f; 1.1 SpringBoot整合MyBatisPlus入门程序 ①&#xff1a;创建新模块&#xff0c;选择Spring初始化&#xff0c;并配置模块相关基础信息 ②&#xff1a;选择当前模块需要使用的技术集&…

win10/11无休眠设置和断电后电池模式自动休眠而不是睡眠-用以省电

1、打开休眠设置选项 打开控制面板\所有控制面板项\电源选项\ 左侧的选择电源按钮的功能 默认状态没有休眠 1、管理员权限打开cmd或者power shell 2、输入一下指令&#xff0c;打开休眠选项 powercfg -hibernate on关闭后重新打开 控制面板\所有控制面板项\电源选项\左侧的选…

PyQt5实战——多脚本集合包,UI以及工程布局(二)

个人博客&#xff1a;苏三有春的博客 系列往期&#xff1a; PyQt5实战——多脚本集合包&#xff0c;前言与环境配置&#xff08;一&#xff09; 布局 2.1 UI页面布局 整体框架分为分为三个部分&#xff0c;垂直分布。 第一个部分为功能选择按钮&#xff08;如UTF-8转换&#…

Linux驱动开发(3):字符设备驱动

上一章节我们了解到什么是内核模块&#xff0c;模块的加载卸载详细过程以及内核模块的使用等内容。 本章&#xff0c;我们将学习驱动相关的概念&#xff0c;理解字符设备驱动程序的基本框架&#xff0c;并从源码上分析字符设备驱动实现和管理。 主要内容有如下五点&#xff1a;…

中国逐年最大NDVI数据集(250m)

最大NDVI数据集是指通过遥感技术获取的归一化植被指数&#xff08;NDVI&#xff09;数据&#xff0c;这些数据反映了地表植被覆盖的密集程度。NDVI的数值范围通常为-1到1&#xff0c;其中-1表示完全被水覆盖&#xff0c;0表示有岩石或裸土等非植被覆盖&#xff0c;而1表示植被完…

【深度学习滑坡制图|论文解读3】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法

【深度学习滑坡制图|论文解读3】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法 【深度学习滑坡制图|论文解读3】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法 文章目录 【深度学习滑坡制图|论文解读3】基于融合CNN-Transformer网络和…

《西部皮革》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《西部皮革》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《西部皮革》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;四川省经济和信息化厅 …

供应SW1108P集成氮化镓直驱的高频准谐振IC

1. 概述 SW1108P 是一款针对离线式反激变换器的高性能高集成度准谐振电流模式 PWM 控制器。 SW1108P 内置 6V 的驱动电压&#xff0c;可直接用于驱动氮化镓功率管&#xff1b;芯片工作于带谷底锁定功能 的谷底开启模式&#xff0c;同时集成频率抖动功能以优化 EMI 性能&…

HTML 基础标签——表格标签<table>

文章目录 1. `<table>` 标签:定义表格2. `<tr>` 标签:定义表格行3. `<th>` 标签:定义表头单元格4. `<td>` 标签:定义表格单元格5. `<caption>` 标签:为表格添加标题6. `<thead>` 标签:定义表格头部7. `<tbody>` 标签:定义表格…

使用Nginx作为反向代理和负载均衡器

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Nginx作为反向代理和负载均衡器 引言 Nginx 简介 安装 Nginx Ubuntu CentOS 配置 Nginx 作为反向代理 配置 Nginx 作为负载…

「实战应用」使用 DHTMLX 构建数据透视表,促进有效的数据分析

概述&#xff1a;在本文中&#xff0c;您将了解组织何时选择数据透视表来处理大数据&#xff0c;并熟悉用于在网页上实现此功能的 DHTMLX 工具。 如今&#xff0c;高效处理大数据已成为企业的必需。企业每天必须处理大量数据&#xff0c;以将其转化为可操作的见解并获得竞争优…

Stable diffusion 3.5本地运行环境配置记录

1.环境配置 创建虚环境 conda create -n sd3.5 python3.10Pytorch(>2.0) conda install pytorch2.2.2 torchvision0.17.2 torchaudio2.2.2 pytorch-cuda12.1 -c pytorch -c nvidiaJupyter能使用Anaconda虚环境 conda install ipykernel python -m ipykernel install --user …

openGauss数据库-头歌实验1-4 数据库及表的创建

一、创建数据库 &#xff08;一&#xff09;任务描述 本关任务&#xff1a;创建指定数据库。 &#xff08;二&#xff09;相关知识 数据库其实就是可以存放大量数据的仓库&#xff0c;学习数据库我们就从创建一个数据库开始吧。 为了完成本关任务&#xff0c;你需要掌握&a…

图解TCP三次握手:一步步构建网络会话

在互联网通信中&#xff0c;确保数据传输的可靠性至关重要。TCP三次握手的过程正是为了解决这一问题而设计的。在建立连接之前&#xff0c;客户端和服务器需要确认彼此的存在与准备状态&#xff0c;以防止因网络延迟或数据丢失而导致的错误。通过三次握手&#xff0c;双方不仅能…