继承知识及扩展(C++)

1. 继承是什么?

继承是面向对象编程的三大特征之一,也是代码复用的手段之一。之前我们在很多的地方尝试函数的复用,而继承是为了类的复用提供了很好的方式。

(1)继承的代码怎么写

在一个类后面使用 :继承方式 类名 表示以某种方式继承某个类

下面的代码的意思是学生类和老师类都继承了人类,而且是public继承(这个是什么意思请往后看),也就是说,老师和学生的public成员都可以访问对应人类的public成员,protected成员可以访问对应的protected成员(这个可以看后面关于继承方式对基类成员访问的影响)

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "Gogo"; int _age = 18; 
};class Student : public Person
{ 
protected:int _stuID; // 学号 
};class Teacher : public Person
{
protected:int _jobid; // 工号 
};

(2)三种继承方式

三种访问限定符对应三种继承方式

访问限定符:public        protected        private

继承方式:    public        protected        private

基类/父类:指的是被继承的类

派生类/子类:指的是继承的父类的类

类成员\继承方式public继承protected继承private继承
基类public成员派生类的public成员派生类的protected成员派生类的private成员
基类protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类private成员派生类中不可访问派生类中不可访问派生类中不可访问

三种访问限定符的大小:public > protected > private

一般来说,派生类的成员的访问方式是继承方式和成员在基类的访问方式的较小的那个

(a)其实,我们多数时候用的都是public继承,因为后两种继承方式的成员只能在派生类中使用,扩展性不够,class默认的继承方式private,struct是public,和成员的默认访问权限是一样的;

(b)基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就需要定义为protected,由此可以看出,protected限定符是因继承才出现的。

2. 赋值转换

看回刚刚那个例子,学生类作为人类的派生类,可以将自己的对象赋值给基类的对象、指针、引用

注意:基类的对象不能赋值给派生类,因为其没有独属于派生类的成员;基类的指针可以强制类型转换成派生类指针,但是基类的指针必须指向派生类对象才行。

class Person
{};class Student : public Person
{};int main()
{Student s;Person p = s;     // 派生类对象赋值给基类对象Person *ptr = &s; // 派生类对象赋值给基类指针Person &ref = s;  // 派生类对象赋值给基类引用return 0;
}

【图解】

把派生类中基类的部分赋值过去,这就是所谓的切片或切割,很形象对不对。

3. 隐藏/重定义

在继承关系中,基类和派生类都回有各自的作用域。因此当两者存在同名成员的时候,派生类使用这个名字的成员时,会调用自己类内部的,这被称为隐藏,也叫做重定义

当然,如果想访问父类的同名成员,也可以用域作用限定符 :: 来进行访问

【例子】

class Person
{
public:void f(int x){cout << x << endl;}
protected:int _telephone = 10086;
};class Student : public Person
{
public:void fun(){cout << _telephone << endl;}void f(double x){cout << x << endl;}   protected:int _telephone = 10010;
};int main()
{Student s;s.fun(); // 10010,子类对象对父类成员变量的隐藏s.f(1.23); // 调用子类的f函数,对成员函数的隐藏s.Person::f(1); // 调用父类的f函数return 0;
}

注意:虽然是合法的,但是在继承体系中最好不要定义同名的成员

4. 默认成员函数

关于默认成员函数是什么,可以翻阅我之前的文章,包括类和对象的基础知识都需要了解,这部分内容才能看懂。而在继承的体系中,也有一些特殊的规则需要了解。

构造函数:自动调用基类中的构造函数初始化基类部分的成员,若基类中无默认的构造函数,则需要显示调用基类的构造函数。

先调用基类的构造,再调用派生类的,因为先定义的先构造。

拷贝构造:调用基类的拷贝构造完成对基类成员的拷贝构造

赋值重载:调用基类的赋值重载实现对基类成员的赋值

析构:先调用派生类的析构,再调用基类的析构函数,和构造的顺序正好相反

class Person
{
public:Person(const string &name, int age): _name(name), _age(age){cout << "Person()" << endl;}Person(const Person &p): _name(p._name), _age(p._age){cout << "Person(const Person& p)" << endl;}Person &operator=(const Person &p){cout << "Person& operator=(const Person& p)" << endl;if (this != &p){_name = p._name;_age = p._age;}return *this;}~Person(){cout << "~Person()" << endl;}private:string _name = "Gogo";int _age = 18;
};class Student : public Person
{Student(const string &name, int age, int id): Person(name, age)  // 调用基类的构造函数初始化基类的部分成员, _stuID(id)  // 初始化派生类的成员{cout << "Student()" << endl;}Student(const Student &s): Person(s) // 调用基类的拷贝构造函数完成基类成员的拷贝构造,_stuID(s._stuID) // 拷贝构造派生类的成员{cout << "Student(const Student& s)" << endl;}Student &operator=(const Student &s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s); // 调用基类的operator=完成基类成员的赋值,切片_stuID = s._stuID;    // 完成派生类成员的赋值}return *this;}~Student(){cout << "~Student()" << endl;// 派生类的析构函数会在被调用完成后自动调用基类的析构函数}private:int _stuID; 
};

【重点】

1. 派生类的赋值运算符重载和基类的函数名相同,构成隐藏,在派生类当中调用基类的赋值重载要用域作用限定符

2. 因为多态的需要,基类和派生类的析构会统一成 destructor() 。因此,要调用父类的析构函数需要域作用限定符


5. 友元和静态成员 

友元关系不能继承,当基类的友元函数想访问派生类的私有和保护是不行的。要想访问,必须得成为派生类的友元才能实现。

静态成员:已经定义了一个静态成员,那么在一个继承体系中只能有一个该名称的静态成员

class Person
{
public:Person(){_count++;}Person(const Person& p) {_count++;}
protected:string _name = "Gogo";int _age = 18;
public:static int _count;
};class Student : public Person
{
protected:int _stuID;
};int Person::_count = 0; // 静态成员变量在类外初始化int main()
{Student s1;Student s2;Student s3(s1);cout << Person::_count << endl;  // 运行结果为3cout << Student::_count << endl; // 也为3return 0;
}


6. 单继承多继承菱形继承

单继承:一个子类只有一个父类

多继承:一个子类有两个或两个以上的直接父类

菱形继承:多继承的意外

菱形继承看起来平平无奇,但是其实他是存在一定的问题的,比如如果创建了一个Assistant的对象,如果给 _name 成员赋值,其实是无法明确是赋给 Student 还是 Teacher 的成员。

当然我们也可以通过域作用限定符来指定那个 _name ,可以解决二义性的问题,但是没有办法解决冗余的问题,因为 Assistant 对象在 Person 的成员会存在两份。

Assistant a;
a.Student::_name = "Micheal";
a.Teacher::_name = "Micheal";

7. 菱形虚拟继承

这个算是菱形继承的解决方案,那么我们来比较一下普通菱形继承和菱形虚拟继承有什么区别吧

【菱形继承】

class Person
{
public:int _person;
};
class Student : public Person
{
public:int _student;
};
class Teacher : public Person
{
public:int _teacher;
};
class Assistant : public Student, public Teacher
{
public:int _assistant;
};int main()
{Assistant ass;ass.Student::_person = 1;ass.Teacher::_person = 2;ass._student = 3;ass._teacher = 4;ass._assistant = 5;return 0;
}

【菱形虚拟继承】

仅展示变化的部分:

class Student : virtual public Person
{
public:int _student;
};
class Teacher : virtual public Person
{
public:int _teacher;
};

从代码上看,似乎没有多大的变化,但是成员变量在内存中的存储方式发生了改变

【普通菱形继承】

可见,_person在Student和Teacher中各存了一份,导致ass对象中存了两个_person,从而导致二义性和冗余。

【菱形虚拟继承】

而菱形虚拟继承是在原先放_person对象的位置存放了公共虚基类成员变量的地址,保证两个_person存在同一个地址,_student和_teacher都能通过地址找到同一个_person。

【总结】

一般不建议使用菱形继承,会导致代码的复杂性和性能出现问题,而且可能使得代码难以分析。

8. 继承和组合

【代码对比】

// 继承
class Car
{
protected:string _colour; string _num; 
};
class BMW : public Car
{
public:void Drive(){cout << "this is BMW" << endl;}
};
// 组合
class Tire
{
protected:string _brand; size_t _size; 
};
class Car
{
protected:string _colour;string _num; Tire _t; 
};

1. 继承是 is-a 的关系,组合是 has-a 的关系;

解释:BMW is a Car. / Car has a Tire.

2. 继承在一定程度上破坏了基类的封装,也就是基类的改变对派生类有很大的影响,依赖关系很强,也就是所谓的耦合度高,也称为白箱复用。组合则相反,属于黑箱复用,耦合度较低。

3. 所以在实践中尽可能多使用组合,提高代码的维护性。不过多态的实现必须依赖继承。

如果你能看到这里,给你点个赞!

如果觉得这篇文章不错的话,不妨点个赞支持一下,你的支持是我持续更新的动力~

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

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

相关文章

程序设计——前后端分离实现简单表白墙

文章目录 一、前端页面样式代码二、前后端衔接1. 后端创建 maven 项目2. 针对前后端交互的解释以及后端代码的实现针对 post 请求解释前后端衔接针对 Get 请求解释前后端衔接 3.后端与数据库的联系以及对数据的存取单独封装数据库连接代码解释后端存储 save 数据的代码解释后端…

森林消防泵:守护绿色生命线的无声战士/恒峰智慧科技

在广袤无垠的森林中&#xff0c;生命的绿色如同一块巨大的调色板&#xff0c;为世界增添了无尽的生机与活力。然而&#xff0c;这美丽的画卷也可能因一场突如其来的火灾而瞬间破碎。因此&#xff0c;有一群默默无闻的消防人员&#xff0c;他们配备的是一台台强大的森林消防泵&a…

MYSQL数据库专业术语及创建数据表详细讲解{sql语句创建数据库语句及条件子句解析,编码格式解析,创建数据表解析,表定义字段解析,主键约束解析}

MYSQL数据库中的专业术语 数据库&#xff08;Database&#xff09;&#xff1a;存储数据的集合&#xff0c;是数据的逻辑容器。 表&#xff08;Table&#xff09;&#xff1a;数据库中存储数据的结构&#xff0c;由行&#xff08;记录&#xff09;和列&#xff08;字段&#x…

第VI章-Ⅰ Vue3生命周期探讨

第VI章-Ⅰ Vue3生命周期探讨 简介Vue3生命周期概览生命周期钩子在选项式 API 中的使用错误捕获钩子 onErrorCaptured 生命周期钩子在组合式 API 中的使用错误捕获钩子 onErrorCaptured 总结 简介 在 Vue 3 中&#xff0c;生命周期钩子定义了组件在其创建、挂载、更新和销毁等过…

设计模式学习笔记 - 项目实战三:设计实现一个支持自定义规则的灰度发布组件(实现)

概述 上两篇文章&#xff0c;我们讲解了灰度组件的需求和设计的思路。不管之前讲的限流、幂等框架&#xff0c;还是现在讲的灰度组件&#xff0c;功能性需求都不复杂&#xff0c;相反&#xff0c;非功能性需求是开发的重点。 本章&#xff0c;按照上篇文章的灰度组件的设计思…

网络基础「HTTPS」

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 腾讯云远程服务器 文章目录 1.基本概念1.1.HTTP协议面临的问题1.2.加密与解密1.3.数字摘要1.4.数字签名 2.解决方案2.1.「对称式加密」2.2.「非对…

MySql#MySql数据库基础

目录 一、什么是数据库 二、主流数据库 三、基本使用 1.连接服务器 2.使用 1.查看你数据库 2.创建数据库 ​编辑 ​编辑 ​编辑​编辑 3.使用数据库 ​编辑 4.创建数据库表 5.表中插入数据 6.服务器&#xff0c;数据库&#xff0c;表之间的关系 四、MySQL架构…

【算法】深度优先搜索岛屿数量

1、题目描述 有一个由0和1组成的二维矩阵&#xff0c;其中1代表陆地&#xff0c;0代表水&#xff0c;岛屿由水平或垂直方向上相邻的陆地连接形成。 假设矩阵的四周均被水包围&#xff0c;请计算岛屿的数量。 输入&#xff1a;matrix [[1,1,0,0],[0,0,1,0],[0,0,0,0],[0,0,1,1],…

for...in 可以用const声明item

代码&#xff1a; function* foo() {yield 1;yield 2;yield 3;}const genr foo();for (const item of genr) {console.log(item);}for (const i 0; i < 5; i) {console.log("i", i);}在这两段代码中&#xff0c;尽管两者都包含 for 循环&#xff0c;但它们的用途…

如何在 Gin 框架中处理多个 websocket 连接?

在Gin框架中处理多个WebSocket连接&#xff0c;你可以使用gorilla/websocket包。以下是一步步的指南&#xff1a; 首先&#xff0c;在你的终端运行go get github.com/gorilla/websocket来安装gorilla/websocket包。 创建一个Connection结构体来保存WebSocket连接和发送通道。 …

Git在无法访问github的访问方法

Git无法下载github上的源代码 代理的情况 问题&#xff1a;Failed to connect to github.com port 443 after 21100 ms: Couldnt connect to server 提示我们需要为Git单独配置代理。 查看我们的代理端口  为git 设置全局代理 git config --global http.proxy 127.0.0.1:&l…

C++中的回溯搜索法(Backtracking)

回溯搜索法&#xff08;Backtracking&#xff09;是一种通过试错的方法来解决问题的策略。在C中&#xff0c;这种方法通常用于解决诸如组合问题、划分问题、排列问题等&#xff0c;尤其在涉及到约束满足问题&#xff08;CSP&#xff0c;Constraint Satisfaction Problem&#x…

在C++中二维数组初始化的几种不同方法

在 C 中初始化二维数组可以有几种不同的方法&#xff0c;这取决于你想要的数组类型和初始化数据的具体情况。以下是一些常用的初始化方法&#xff1a; 1. 静态初始化 如果你知道数组的大小和初始值&#xff0c;可以直接在声明时初始化。这种方法使用嵌套的大括号 {} 来逐行指…

Apache反代理Tomcat项目,分离应用服务器和WEB服务器

项目的原理是使用单独的机器做应用服务器&#xff0c;再用单独的机器做WEB服务器&#xff0c;从网络需要访问我们的应用的话&#xff0c;就会先经过我们的WEB服务器&#xff0c;再到达应用程序&#xff0c;这样子的好处是我们可以保护应用程序的机器位置&#xff0c;同时还可以…

LNMP一键安装包

LNMP一键安装包是什么? LNMP一键安装包是一个用Linux Shell编写的可以为CentOS/RHEL/Fedora/Debian/Ubuntu/Raspbian/Deepin/Alibaba/Amazon/Mint/Oracle/Rocky/Alma/Kali/UOS/银河麒麟/openEuler/Anolis OS Linux VPS或独立主机安装LNMP(Nginx/MySQL/PHP)、LNMPA(Nginx/MySQ…

代码随想录leetcode200题之链表

目录 1 介绍2 训练3 参考 1 介绍 本博客用来记录代码随想录leetcode200题中链表部分的题目。 2 训练 题目1&#xff1a;203移除链表元素 C代码如下&#xff0c; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* Lis…

【学一点儿前端】Bad value with message: unexpected token `.`. 问题及解决方法

问题 今天从vue3的项目copy一段代码到vue2项目&#xff0c;编译后访问页面报错了 Bad value with message: unexpected token ..注意到错误字符‘.’&#xff0c;这个错误通常发生在处理 JavaScript 或者 HTML 中的动态表达式中&#xff0c;日常使用二分法不断缩小报错代码范…

JavaScript String indexOf() 方法

一、定义和用法&#xff1a; indexOf() 方法返回值在字符串中第一次出现的位置。 如果未找到该值&#xff0c;则 indexOf() 方法返回 -1。 indexOf() 方法区分大小写。 二、语法 string.indexOf(substring, start) 1、参数 substring必需。要搜索的字符串。start可选。开…

2024“天一永安杯“宁波第七届网络安全大赛极安云科战队部分WP

“天一永安杯”2024 宁波第七届网络安全大赛暨第九届大学生网络技术与信息安全大赛 大赛竞赛形式 一、线上初赛 参赛人员&#xff1a;各单位自行选拔3人&#xff08;设队长1名&#xff09;组成团队&#xff0c;不足3人不允许参赛。 竞赛时间&#xff1a;8&#xff1a;30-12&…

LLMs:《Better Faster Large Language Models via Multi-token Prediction》翻译与解读

LLMs&#xff1a;《Better & Faster Large Language Models via Multi-token Prediction》翻译与解读 目录 《Better & Faster Large Language Models via Multi-token Prediction》翻译与解读 Abstract 2、Method方法 Memory-efficient implementation 高效内存实…