【C++】——继承(详解)

一  继承的定义和概念

1.1  继承的定义

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类被继承的称为基类

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。 

下面由代码来进行理解吧 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
protected:string _name = "喜羊羊";int _age = 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

这段代码分为两个个部分 

class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
protected:string _name = "喜羊羊";int _age = 21;
};

这个也就是我们之前写的class类的写法

第二部分

class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};

 对于学生和老师来说,他们都有一个共同的特性,那就是人,那我们把人的属性封装起来,然后用老师和学生去复用他,你也就达到了目的,这里的复用也可以称之为继承。

那继承的方法是什么呢?

class 新类的名字:继承方式 继承类的名字{};

就和这个例子一样 

class Student : public Person
{
protected:int _stuid; // 学号
};

这里的Student和Tercher我们规范里面称之为派生类,对于Person类来说我们称之为基类。

但是一般来说我们会称之为子类和父类。

1.2 继承的访问权限

由上面我们知道,public是一种继承的方式,但是还有其他的,他们继承方式的不同,访问权限也就不一样

可以看出全部组合起来有很多,但是我们发现一个规律就是他们都是向下取舍的。

我们知道它们三个的权限大小分别为 public>protected>private ,所以如果我们把它们两两组合,取的是权限小的那一个。

1.3 继承的细节

1.如果是private成员,那么无论什么方式都是不可见的,这里不可见是指派生类在类里面和类外面都不能访问,但是它们确确实实是继承了

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" <<_name<< endl;cout << "age:" << _age << endl;}
private:string _name = "喜羊羊";int _age = 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};

上面代码中Person成员变量变为私有,但是确确实实是继承了,唯一的就是不能访问

 

 

2.对于protected来说,它不像private那样那么严格,它可以让你在类里面访问它,但是在类外面也不能访问。可以看出保护成员限定符是因继承才出现的。

3. 关键字struct默认继承的方式是public,class默认继承方式是private;

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

我们之前的类型转换是创建一个临时变量然后进行赋值,但是这里不一样,这里采用的切片的方式进行的

也就是说子类给父类的时候,会进行一个类似切片的操作,把指给父类,但是如果是父类给子类,那么就会有问题了,因为父类没有子类的_No成员,所以给不了它相应的值。

这里也可以用指针和引用进行操作,但是也有许多情况

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
protected:string _name;string _sex;int _age;
};
class Student : public Person
{
public:int _No; // 学号
};
int main()
{Student sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象//sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobj;Student* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问ps2->_No = 10;return 0;
}

 这里着重看一下后面这段

    pp = &sobj;Student* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问ps2->_No = 10;

这里的第一部分pp实际上是指向student对象,所以这里ps1也指向student对象,所以可以访问里面的成员

但是第二部分pp是指向Person对象,这里直接强转,相当于告诉编译器忽略这个事实,虽然也是转了,ps2也指向这个对象,但是里面没有_No这个成员,所以会发生越界访问的存在

三  继承中的作用域

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

 3.1  同名成员变量

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected:string _name = "小李子"; // 姓名int _num = 111;// 身份证号
};
class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号
};
void Test()
{Student s1;s1.Print();
};

 

 void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}

我们在打印身份证号的时候采用了指定类域的方式,如果不这样,那么就会导致编译器分不清,那么就直接打印子类的成员了

 

 3.2  同名成员函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};

 B中的fun和A中的fun不是构成重载,因为不是在同一作用域
 B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

 四  派生类的默认成员函数

4.1  构造函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}
protected:int _age;
};int main()
{student st("沸羊羊", 18);return 0;
}

对于构造来说,它是先调用父类的构造函数,然后再调用子类的构造函数

子类的构造函数没有去初始化父类的,这里是编译器自己去调用父类的默认构造函数,跟自定义类型是一个道理。

4.2  析构函数

这里析构函数和构造函数相反,这里是先调用子类的,再调用父类的

这里也可以用先构造的后析构,后构造的先析构来解释

class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}
protected:int _age;
};

 如果我们在子类中调用父类的析构,如果是指针类型,就会被析构两次。其次之所以先调用子类的析构后调用父类的析构,是因为可能析构的时候存在某些记录工作,所以不能先调用父类的析构函数

4.3  拷贝构造

派生类对象通常包含基类部分和派生类特有的部分。当创建一个派生类对象的副本时,我们需要确保这两部分都被正确地复制。基类部分的复制是通过调用基类的拷贝构造函数来完成的。

如果不调用父类的拷贝构造,那么就会导致资源缺失

class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}Person(const Person&s):_name(s._name){}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}student(const student&s) :Person(s),_age(s._age)//这里Person的拷贝,直接传一个s过去切片就行{}
protected:int _age;
};

这里的拷贝构造重点就在于巧妙运用了Person的切片,其实这里可以不写,编译器默认生成的就够用了,但是我们要显示调用就必须这样写

4.4  赋值运算符重载

class Person {
public:Person(string name = "喜羊羊"):_name(name){cout << name << endl;}~Person(){cout << "~Person()" << endl;}Person(const Person&s):_name(s._name){}Person& operator=(const Person&s){if (this != &s){_name = s._name;}return *this;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}~student(){cout << "~student()" << endl;}student(const student&s) :Person(s),_age(s._age){}student& operator=(const student& s){if (this != &s){Person::operator=(s);_age = s._age;}return *this;}
protected:int _age;
};

这里的赋值运算符重载需要指定类域,不然就会出现死循环,一直调用自己的成员函数,因为这两个是同名的,属于隐藏关系。

五 单继承和多继承

单继承:

一个子类只有一个父类

多继承:

一个子类有多个父类

菱形继承:

菱形继承是多继承的一个特例,同时菱形继承也弄出了很多问题

1.在菱形继承中,Student和Teach都继承了Person里面的成员变量a,后面Assistant继承了它们两个,那么Assistant是不是有两个a呢,这里就是数据的二义性。

2.对于二义性我们可以加访问限定符去解决,但是我们还是存在一个问题就是有两个a,我们继承下来就想要一个,这个就是数据冗余,对于数据冗余我们能用的就是虚继承去解决

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:int _a;
};
class B :  public A
{
public:int _b;
};
// class C : public A
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d._a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

如果我们直接访问

    d._a = 1;

但是我们加上就欧克了

d.B::_a = 1;

这里也就解决数据二义性的问题,但是对于数据冗余,我们采用虚继承,这里继承以后就是把_a当作成共有的了

那么虚继承就是 会在类B和类C里面生成一个虚基表指针,这指针指向一张表,表里面存有偏移量,然后通过这个偏移量找到_a,所以这里的_a是共有的。

 

六  继承其他问题

1. 继承如果父类有友元函数,继承以后,友元函数是不能被子类使用的。

2. 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。这里也可以理解为是共有的,所以不会重复。

七  总结

以上就是继承的全部内容了,希望对你有所帮助

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

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

相关文章

CentOS7下快速升级至OpenSSH9.7p2安全版本

一、CentOS7服务器上编译生成OpenSSH9.3p2的RPM包 1、编译打包的shell脚本来源于该项目 https://github.com/boypt/openssh-rpms解压zip项目包 unzip openssh-rpms-main.zip -d /opt cd /opt/openssh-rpms-main/ vim pullsrc.sh 修改第23行为source ./version.env 2、sh pull…

C语言,struct 结构体、union共用体的使用

//状态字节&#xff0c;根据数据定义几个标志&#xff0c;标志位依据联合体内部结构体进行变量定义 //目的&#xff0c;节省内存空间&#xff0c;省去特定字节 struct STATDATA {union{unsigned char stat;struct {unsigned stat0:1;unsigned stat1:1;unsigned stat2:1;unsign…

[线程与网络] Java虚拟机常考面试题(线程与网络完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

Atlassian企业日技术分享:AI在ITSM中的创新实践与应用、Jira服务管理平台AI功能介绍

2024年5月17日&#xff0c;Atlassian中国合作伙伴企业日活动在上海成功举办。活动以“AI协同 创未来——如何利用人工智能提升团队协作&#xff0c;加速产品交付”为主题&#xff0c;深入探讨了AI技术在团队协作与产品交付中的创新应用与实践&#xff0c;吸引了众多业内专家、企…

光伏项目管理——数字化改革

随着全球对可再生能源的迫切需求以及环保意识的日益增强&#xff0c;光伏产业作为清洁能源的重要组成部分&#xff0c;正迎来快速发展的黄金时期。然而&#xff0c;传统的光伏项目管理方式已逐渐无法满足现代化、高效化的需求&#xff0c;数字化改革成为了行业发展的必然趋势。…

DeepSORT(目标跟踪算法)中卡尔曼滤波器中的更新

DeepSORT&#xff08;目标跟踪算法&#xff09;中卡尔曼滤波器中的更新 flyfish 说协方差先说期望 在协方差的定义中&#xff0c;符号 E \mathbb{E} E 表示期望值&#xff08;Expectation&#xff09;。期望值是随机变量的平均值或均值&#xff0c;表示在大量试验中随机变量…

什么是 URL 过滤?是如何保障浏览体验的?

互联网是一个无边无际的空间&#xff0c;几乎包含了你能想象到的一切。不幸的是&#xff0c;这意味着也存在着从不合适到非常危险的网站。这就是 URL 过滤可以发挥作用的地方。 一、URL 过滤的含义 我们希望您已经熟悉 URL&#xff08;统一资源定位器&#xff09;&#xff0c;…

Java MyBatis实战:QueryWrapper中的and和or拼接技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在Java Web开发中&#xff0c;MyBatis是一个非常流行的持久层框架。它通过XML或注解的方式将Java对象与数据库表进行映射&#xff0c;从而实现数据的增删改查操作。在使用MyBatis的过程中&#xff0c;经常…

进程和内存管理

描述&#xff1a; 内存的使用和剩余情况当前cpu的负载情况找进程的id结束某个进程 检查内存&#xff1a; 方法一&#xff1a; /proc/meminfo 文件这是一个伪文件这个文件&#xff0c;纪录了内存的相关信息不用用vi打开&#xff0c;应该用cat查看 方法二&#xff1a; 命令…

Qt程序打包成单个exe文件

文章目录 0. 准备工作1. 使用 windeployqt 提取必要的动态链接库和资源文件1.1 操作步骤1.2 补充 2. 使用 Enigma Virtual Box将文件夹打包成单个exe2.1 操作步骤 0. 准备工作 Qt程序打包用到的工具有&#xff1a; windeployqt &#xff1a;安装Qt时自带Enigma Virtual Box 下…

重温共射放大电路

1、放大概念 小功率信号变成一个大功率信号&#xff0c;需要一个核心器件做这件事&#xff0c;核心器件的能量由电源提供&#xff0c;通过核心器件用小功率的信号去控制大电源&#xff0c;来实现能量的转换和控制&#xff0c;前提是不能失真&#xff0c;可以用一系列正弦波进行…

SpringBoot之静态资源

默认静态资源路径 classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/ 静态资源路径下的文件&#xff0c;可以通过根目录访问 resources 文件夹的文件如下图所示&#xff1a; 启动项目&#xff0c;分别访问以下路径&#xff1a; ht…

解锁 DevOps 精通:成功的综合指南

在动态的软件开发领域&#xff0c;要掌握 DevOps&#xff0c;需要对其核心原则有细致的了解&#xff0c;并采取战略性实施方法。DevOps 是一种协作方法&#xff0c;它将软件开发 (Dev) 和 IT 运营 (Ops) 结合起来&#xff0c;以自动化和简化软件交付流程。它旨在缩短开发周期、…

CleanMyMac X软件最新版下载【安装详细图文教程】

​CleanMyMac X是一款专业的Mac清理软件&#xff0c;可智能清理mac磁盘垃圾和多余语言安装包&#xff0c;快速释放电脑内存&#xff0c;轻松管理和升级Mac上的应用&#xff0c;同时CleanMyMac X可以强力卸载恶意软件&#xff0c;修复系统漏洞&#xff0c;一键扫描和优化Mac系统…

外星人Aurora R15 intel版 原厂Windows11oem系统

装后恢复到您开箱的体验界面&#xff0c;包括所有原机所有驱动AWCC、Mydell、office、mcafee等所有预装软件。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http://w…

【机器学习】GANs网络在图像和视频技术中的应用前景

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 1. &#x1f525;引言 背景介绍 研究意义 2. &#x1f388;GANs的基本概念和工作原理 生成对抗网络简介 工作原理 3. &#x1f916;GANs在图像生成中的应用 图像超分辨率 工作原理 图像去噪 工作原理 图…

目标检测6:采用yolov8, RK3568推理的性能

最近有个小伙伴&#xff0c;问我rk3568上推理图片&#xff0c;1秒能达到多少&#xff1f; 本次采用模型为yolov8s.rknn&#xff0c;作了一次验证。 解析一段视频文件&#xff0c;1280*720, fps 24。读取视频文件&#xff0c;然后进行推理。 通过性能优化&#xff0c;发现推理…

Apple ID已成历史,在ios18中正式更名为Apple Account

随着iOS18的首个开发者预览版成功推送&#xff0c;众多热衷于尝鲜的用户已纷纷升级并开启全新体验。在这个版本中&#xff0c;备受瞩目的Apple ID正式迎来了它的进化——更名为Apple Account&#xff0c;并且拥有了中文名称“Apple账户”或简称“苹果账户”。 不过目前官网还称…

【全开源】Workerman在线客服系统(ThinkPHP+FastAdmin+Workerman)

Workerman在线客服系统&#xff1a;高效沟通的新选择 基于ThinkPHPFastAdminWorkerman开发的一款实时在线客服系统&#xff0c;支持多客服(不限座席)、知识库、离线留言板、离线消息、历史会话、微信小程序接入、Uni-app接入(高级授权)、用户轨迹等功能。​ &#x1f4e2; 一…

AI大模型智慧政务解决方案

随着AI大模型技术的蓬勃发展和普及应用&#xff0c;我们的政务治理正迎来一场波澜壮阔的革新巨浪。这场革新&#xff0c;不仅是技术层面的飞跃&#xff0c;更是一场深刻改变治理理念的伟大变革。它彻底颠覆了传统政务治理中依赖人力、效率低下、响应迟缓的“人盯人”模式&#…