C++ 继承

前言

本文将会为你带来关于继承的相关知识(概念、定义、基类的成员变量访问形式、隐藏、父子类之间的赋值、派生类的默认成员函数、菱形继承与虚继承)

继承的概念以及定义

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
以下是一个继承的简单例子,student和teacher类继承了person类,student和teacher类是person的子类(派生类),person类是父类(基类)
我们可以观察到,子类的对象从父类继承到了父类的_name, _age
在这里插入图片描述

//父类Person
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; //姓名int _age = 18; //年龄
};//子类student
class Student : public Person
{
public:void func(){Print();}
protected:int _stuid; //学号
};//子类teacher
class Teacher : public Person
{
protected:int _jobid; //工号
};

基类的成员变量在派生类中的访问形式

通过上面一个例子,我们可以观察到父类的成员有访问限定符的限制,而继承的时候
也会出现访问限定符,那这些组合在一起会有怎样的效果呢?
在这里插入图片描述
在这里插入图片描述

其中需要注意的是基类private成员在派生类无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
图中我们也可以观察到:基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)

剩下的组合需要大家一个个去尝试感受

//父类Person
class Person
{
//protected:
public:
//private:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; //姓名int _age = 18; //年龄
};
//子类student
class Student : public Person
{
public:void func(){Print();}
protected:int _stuid; //学号
};
class Student : protected Person
{
public:void func(){Print();}
protected:int _stuid; //学号
};
//子类teacher
class Teacher : public Person
{
protected:int _jobid; //工号
};
int main()
{Student s;Teacher t;s.Print();//t.Print();return 0;
}

隐藏

1、在继承体系中基类和派生类都有独立的作用域,因此父类和子类中是允许存在同名的成员的 2、 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员 显示访问) 3、 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。 4、 注意在实际中在继承体系里面最好不要定义同名的成员。 如果这里直接对_num成员变量进行访问,对父类同名成员的直接访问会被屏蔽掉

在这里插入图片描述

class person
{
protected:string _name = "小梨子";int _num = 111;
};
//stu派生类
class student : public person
{
public:void print(){//父类和子类可以有同名成员,因为他们是独立作用域cout << " 姓名:" << _name << endl;//隐藏cout << " 身份证号:" << person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; //学号
};class a
{
public:void fun(){cout << "func()" << endl;}
};class b : public a
{
public:void fun(int i){cout << "func(int i)->" << i << endl;}
};void test()
{b b;b.fun(10);//error:b.fun();b.a::fun();
}int main()
{student s;s.print();test();return 0;
}

基类和派生类对象赋值转换

当类型转换的时候,会产生一个临时变量,而临时变量具有常性,但是如果是父类赋值给子类对象,我们认为这是天然的,中间不产生临时对象,不需要对父类对象加const,这个叫做父子类的赋值兼容规则,但是需要注意不能反过来赋值(即父类赋值给子类)

在这里插入图片描述

//父子类的赋值兼容规则
class Person
{
protected:string _name;string _sex;int _age;
};
class Student : public Person
{
public:int _No;
};
int main()
{//类型转换时,产生一个临时对象,具有常性,需要加constdouble d = 2.2;int i = d;const int& r = d;//类型转换时,产生一个临时对象,具有常性,需要加conststring str = "xxxx";const string& rstr = "xxxx";//子类可以赋值给父类,它会把子类部分切割出来赋值给父类Student s;Person p = s;Person& rp = s;return 0;
}

派生类的默认成员函数

1、 子类的构造函数必须调用父类的构造函数初始化基类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显示调用。 2、 子类的拷贝构造函数必须调用父类的拷贝构造完成基类的拷贝初始化。 3、 子类的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;}
public:string _name;
};
//子类
class Student : public Person
{
public://构造Student(const char* name, int id):_id(id)//error:, _name(name),Person(name){cout << "student(const char* name, int id)" << endl;}//拷贝构造Student(const Student& s):Person(s),_id(s._id){cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){person::operator=(s);_id = s._id;}return *this;}//析构~Student(){//Person::~Person();cout << "~Student()" << endl;}
protected:int _id;
};
int main()
{Student s1("张三", 18);Student s2(s1);       Student s3("李四", 19);s1 = s3;return 0;
}

继承和静态成员

以下程序输出结果为3,基类定义了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;
};
Student func()
{Student st;return st;
}
int main()
{Person p;Student s;func();cout << Person::_count << endl;
}

菱形继承和虚继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承:菱形继承是多继承的一种特殊情况,有数据冗余和二义性的问题

在这里插入图片描述

如下代码中出现了virtual,virtual是什么意思呢,我们待会再谈
在这里插入图片描述
在这里插入图片描述

我们先来看看程序编译的结果:148行报了访问不明确的错误
在这里插入图片描述

我们透过监视窗口可以观察到a对象里面是存在两份person基类中的_name的成员变量的,a对象中出现了两份数据,这两份数据从基类继承过来,如果我们通过对象a直接访问_name,编译器并不知道你指的是小张还是老张这就会有二义性的问题,若是person中还涉及了人的身份证信息等,那么会导致a对象继承两份身份证信息,这必然会导致数据冗余,若是信息量一大,太过浪费内存空间
在这里插入图片描述

//多继承:菱形继承
class Person
{
public:string _name;int _age;int _tel;
};
//派生类stu
class Student : public Person
//class Student : virtual public Person
{
protected:int _num;
};
//派生类tea
class Teacher : public Person
//class Teacher : virtual public Person
{
protected:int _id;
};
//菱形继承
class Assistant : public Student, public Teacher
{
protected:string _majorCourse;
};void Test()
{Assistant a;a.Student::_name = "小张";a.Teacher::_name = "老张";a._name = "张三";
}

而virtual虚继承可以解决这个事情,注意:virtual加在直接继承person的位置,例如示例代码中加在了teacher类与student类并没有加在Assistant类
当我们加上在对应位置加上virtual后,再来透过监视窗口观察
这个时候a对象继承下来的基类成员都只存在一份,因此对一个地方成员的修改也会影响到其他类中的成员
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

小结

今日的分享就到这里啦,如果本文存在疏漏或错误的地方还请您能够指出

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

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

相关文章

ANR系列之八:疑难ANR问题处理记录

前言&#xff1a; 本文仅是记录作者自身处理过的ANR问题&#xff0c;以及帮助他人解决过的ANR问题。本文中所介绍的ANR处理记录仅供参考&#xff0c;并不适用所有场景。并且最终结论和分析并不一定就是绝对正确的。 案例1.页面切换时前台应用焦点未获得 案例编号&#xff1a;…

无代码的未来

随着无代码技术越来越成熟&#xff0c;很多web应用已经可以基于无代码平台进行开发。本文分析了4个最流行的无代码平台&#xff0c;并梳理了无代码行业今后可能的发展方向。原文: The future of NoCode 所有无代码编辑器都需要回答的问题 当需要选择无代码解决方案时&#xff0…

Vue基础语法核心指令过滤器计算属性监听属性

目录 1. 模板语法 1.1 插值 1.1.1 文本 1.1.2 html 1.1.3 属性 1.1.4 表达式 1.2 指令 1.2.1 核心指令 1.2.1.1 v-if |v-else-if|v-else 1.2.1.2 v-show 1.2.1.3 v-for 1.2.1.4 v-on|v-model|v-for 1.2.1.5 参数 v-bind:href,v-on:click 1.2.1.6 简写 2. 过滤器…

[AUTOSAR][网络管理] 什么是BusOff? 如何实现它?

文章目录 一、BusOff检测机制(1)简介(2)目录介绍(3)软件实现逻辑代码运行流程如下:二、测试方法(1)物理测试三、示例代码(1) busoff_recovery.c(2) busoff_recovery.h一、BusOff检测机制 (1)简介 CAN控制器可以判断出错误的类型是总线上暂时的数据错误(如外部干扰等…

Python中的内存管理:深入分析垃圾回收机制

python中有一个名为refchian的环状双向链表&#xff0c;python运行时创建的所有对象都会添加到refchain中。在refchain中的对象PyObject里都有一个ob_refcnt用来保存当前对象的引用计数器&#xff0c;就是该对象被引用的次数&#xff0c;当对象有新引用时ob_refcnt就会增加&…

西门子博途软件加密保护方法

一、程序块的专有技术保护 程序块的专有技术保护主要是对项目中的程序块&#xff08;OB、FB、FC、DB&#xff09;进行访问保护&#xff0c;如果没有专有技术保护密码则无法看到程序块中的具体内容&#xff0c;对于专有技术保护的 DB 块&#xff0c;如果没有密码只能读不能写。…

eNSP-OSPF协议其他区域不与骨干区域相连解决方法3

virtual-link技术 AR1 [ar1]int g0/0/0 [ar1-GigabitEthernet0/0/0]ip add 192.168.1.1 24 [ar1-GigabitEthernet0/0/0]quit [ar1]ospf [ar1-ospf-1]area 0 [ar1-ospf-1-area-0.0.0.0]net 192.168.1.0 0.0.0.255 [ar1-ospf-1-area-0.0.0.0]quit AR2 [ar2]int g0/0/0 [ar2-Gig…

skiaSharp linux 生成验码字体显示不出来

一、拷贝windows下的字体如&#xff1a;C:\Windows\Fonts 设置字体的地方&#xff1a; var fontPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TAHOMA.TTF");最终效果&#xff1a;

d3dx9_43.dll丢失怎么解决,四个解决方法帮你解决d3dx9_43.dll丢失

随着科技的不断发展&#xff0c;我们越来越依赖各种软件和硬件设备来提高生活和工作效率。然而&#xff0c;有时候我们可能会遇到一些技术问题&#xff0c;如“d3dx9_43.dll丢失”的问题。这个问题可能导致某些程序无法正常运行&#xff0c;给我们的生活带来诸多不便。因此&…

C++初阶--类与对象(1)

文章目录 类的引入类的定义访问限定符类成员的注意事项变量名的冲突 类的实例化类成员的声明与定义类的大小this指针特性 总结 类的引入 在c语言中&#xff0c;我们会这样写一个栈&#xff1a; struct Stack {int* a;int top;int capacity; };void StackInit(struct Stack* p…

线性代数2:梯队矩阵形式

图片来自 Europeana on Unsplash 一、前言 欢迎阅读的系列文章的第二篇文章&#xff0c;内容是线性代数的基础知识&#xff0c;线性代数是机器学习背后的基础数学。在我之前的文章中&#xff0c;我介绍了线性方程和系统、矩阵符号和行缩减运算。本文将介绍梯队矩阵形式&#xf…

Android 如何在Service中使用ViewModel

需求&#xff1a;最近有反馈说&#xff0c;需要在service中使用网络请求&#xff0c;而我网络请求就是封装的ViewModel。然后我就发现&#xff0c;原来service不支持&#xff0c;懵了呀&#xff01;哈哈 还是去看看ViewModel的源码了解下吧。下面有几个介绍的。就不多做赘述了。…

微信小程序开发之后台数据交互及wxs应用

目录 一、后端准备 1. 应用配置 2. 数据源配置 二、数据库 1. 创建 2. 数据表 3. 数据测试 三、前端 1. 请求方法整合 2. 数据请求 3. WXS的使用 4. 样式美化 5. 页面 一、后端准备 通过SpringMVC及mybatis的技术学习&#xff0c;还有前后端分离的技术应用&…

Mycat2 分布式数据库中间件

一.安装部署 Mycat2目前还不支持直接获取Docker镜像&#xff0c;需要自己通过Dockerfile打包镜像&#xff0c;其实这也是为了开发者考虑&#xff0c;比如一些个性化功能&#xff0c;如自定义分片等 Dockerfile FROM docker.io/adoptopenjdk/openjdk8:latestENV AUTO_RUN_DIR…

RemObjects Elements 12.0 Crack

Elements 是一个现代多功能软件开发工具链。 它支持六种流行的编程语言&#xff1a;Oxygene (Object Pascal)、C#、Java、Mercury (Visual Basic.NET™)、Go 和 Swift&#xff0c;适用于所有现代平台。 使用 Elements&#xff0c;您可以为您喜欢的任何平台进行编程- 无论是单…

想要精通算法和SQL的成长之路 - 找到最终的安全状态

想要精通算法和SQL的成长之路 - 找到最终的安全状态 前言一. 找到最终的安全状态1.1 初始化邻接图1.2 构建反向邻接图1.3 BFS遍历1.4 完整代码 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 找到最终的安全状态 原题链接 我们从题目中可以看出来&#xff1a; 出度为0的…

面试官:如何理解CDN?说说实现原理?

一、是什么 CDN (全称 Content Delivery Network)&#xff0c;即内容分发网络 构建在现有网络基础之上的智能虚拟网络&#xff0c;依靠部署在各地的边缘服务器&#xff0c;通过中心平台的负载均衡、内容分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c;降…

大模型技术实践(五)|支持千亿参数模型训练的分布式并行框架

在上一期的大模型技术实践中&#xff0c;我们介绍了增加式方法、选择式方法和重新参数化式方法三种主流的参数高效微调技术&#xff08;PEFT&#xff09;。微调模型可以让模型更适合于我们当前的下游任务&#xff0c;但当模型过大或数据集规模很大时&#xff0c;单个加速器&…

OpenCV中world模块介绍

OpenCV中有很多模块&#xff0c;模块间保持最小的依赖关系&#xff0c;用户可以根据自己的实际需要链接相关的库&#xff0c;而不需链接所有的库&#xff0c;这样在最终交付应用程序时可以减少总库的大小。但如果需要依赖OpenCV的库太多,有时会带来不方便&#xff0c;此时可以使…

忆联分布式数据库存储解决方案,助力MySQL实现高性能、低时延

据艾瑞咨询研究院《2022 年中国数据库研究报告》显示&#xff0c;截止2021年&#xff0c;中国分布式数据库占比达到 20%左右&#xff0c;主要以 MySQL 和 PostgreSQL 为代表的开源数据库为主。MySQL 作为备受欢迎的开源数据库&#xff0c;当前已广泛应用于互联网、金融、交通、…