【C++深度探索】继承机制详解(一)

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C++入门至进阶
这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖

目录

  • 1.继承的概念
  • 2.继承定义
    • 2.1定义格式
    • 2.2访问限定符
    • 2.3继承方式
  • 3.基类和派生类对象赋值转换
  • 4.继承中的重定义(隐藏)
  • 5.派生类的默认成员函数
    • ✨构造函数
    • ✨拷贝构造
    • ✨赋值运算符重载
    • ✨析构函数
  • 6.结语

1.继承的概念

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

2.继承定义

2.1定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

//基类或父类
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";// 姓名int _age = 18;  //年龄
};//派生类或子类
class Student : public Person
{
protected:int _stuid; // 学号int _major;	//专业
};

在这里插入图片描述

2.2访问限定符

C++类的访问限定符用于控制类的成员(包括成员变量和成员函数)在类的外部的可访问性。C++中有以下三种访问限定符:

  • public: 公共访问限定符,任何地方都可以访问公共成员。可以在类的外部使用对象名和成员名直接访问公共成员。

  • private: 私有访问限定符,只有类内部的其他成员函数可以访问私有成员。类的外部无法直接访问私有成员,但可以通过公共成员函数间接访问私有成员。

  • protected: 保护访问限定符,只有类内部的其他成员函数和派生类的成员函数可以访问保护成员。类的外部无法直接访问保护成员,但可以通过公共成员函数或派生类的成员函数间接访问保护成员。

需要注意的是,访问限定符只在类的内部起作用,在类的外部没有直接的影响。同时,访问限定符可以用于类的成员变量和成员函数的声明中,默认情况下,成员变量和成员函数的访问限定符是private。

2.3继承方式

在这里插入图片描述
C++类的继承方式有以下几种:

  • 公有继承(public inheritance):使用关键字"public"表示的继承方式。在公有继承中,基类的公有成员和保护成员都可以在派生类中访问,私有成员不能在派生类中直接访问。
class Base {
public:// 公有成员
protected:// 保护成员
private:// 私有成员
};class Derived : public Base {// 公有继承
};
  • 保护继承(protected inheritance):使用关键字"protected"表示的继承方式。在保护继承中,基类的公有成员和保护成员在派生类中都变为保护成员私有成员不能在派生类中直接访问。
class Base {
public:// 公有成员
protected:// 保护成员
private:// 私有成员
};class Derived : protected Base {// 保护继承
};
  • 私有继承(private inheritance):使用关键字"private"表示的继承方式。在私有继承中,基类的公有成员和保护成员在派生类中都变为私有成员,私有成员不能在派生类中直接访问。
class Base {
public:// 公有成员
protected:// 保护成员
private:// 私有成员
};class Derived : private Base {// 私有继承
};

总结如下:

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

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

这些继承方式可以根据具体的需求选择合适的方式

②基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

例如:

//基类或父类
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}//保护成员
protected:string _name = "tutu";// 姓名int _age = 20;  //年龄//私有成员
private:string _tele = "123456";};//派生类或子类
class Student : public Person
{
public:void sPrint(){Person::Print();	//可以使用父类的公有成员}
protected:int _stuid; // 学号string _sname = _name;//可以访问父类的保护成员_namestring _stele = _tele; //不可以访问父类的私有成员_tele
};

结果如下:
在这里插入图片描述

上述父类Person中成员有三种访问限定分别是public、protected、private,而子类Student使用public继承父类,那么对于父类的公有成员在子类中的访问方式还是public,protected成员访问方式选择继承方式public和protected中较小的protected,同理父类的private成员继承到子类中也是选择private方式,在子类中不可访问

对于私有成员也是被继承到子类中,只是不可访问:

在这里插入图片描述

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

④ 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

⑤在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

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

  • 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

如下图所示:

在这里插入图片描述

  • 基类对象不能赋值给派生类对象

例如下面代码:

//父类
class Person
{
protected:string _name; // 姓名string _sex;  //性别int _age; // 年龄
};
//子类
class Student : public Person
{
public:int _No; // 学号
};
void Test()
{Student sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;//error
}

在这里插入图片描述

4.继承中的重定义(隐藏)

  • 在继承体系中基类和派生类都有独立的作用域。
  • 子类和父类中有同名成员,子类成员将屏蔽对父类同名成员的直接访问,这种情况叫隐藏,也叫重定义。

当一个类继承另一个类时,它可以重定义继承的成员函数或者成员变量。
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

  • 如果要访问被隐藏的父类的同名成员,可以在子类成员函数中,使用 父类::父类成员来显示访问

注意在实际中在继承体系里面最好不要定义同名的成员。

例如:

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
//父类
class Person
{
protected:string _name = "胡土土"; // 姓名int _num = 1234; // 身份证号
};//子类
class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号,与父类的_num重名构成隐藏
};void Test()
{Student s1;s1.Print();
};

结果如下:

在这里插入图片描述

我们发现当子类与父类有隐藏关系时,对于同名变量_num的调用,除非显示使用Person::_num 调用的是父类的成员变量,其他情况_num表示的都得子类中定义的变量,这是因为它们有不同的作用域,在子类中调用变量都是先从子类这个作用域中寻找。

再看下面的例子:

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构成隐藏,成员函数满足函数名相同就构成隐藏。

结果如下:

在这里插入图片描述
如果Test函数中:

void Test()
{B b;b.fun();//这里没有给参数
}

结果如下:

在这里插入图片描述

使用对象b调用fun()没有给参数,这样编译是不通过的,因为这样调用是调用的类B中的成员函数fun是需要传参的,如果要调用基类中的fun函数就必须显示调用,代码如下:

void Test()
{B b;b.A::fun();//显示调用A中的fun函数
}

结果如下:
在这里插入图片描述

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

在这里插入图片描述

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,(先不考虑取地址重载)这几个成员函数是如何生成的呢?

例如如下父类:

//有如下Person父类
class Person
{
public:Person(const char* name = "tutu"): _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; // 姓名
};

✨构造函数

  • 派生类的构造函数必须调用基类的默认构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用基类的构造函数。
//基于上面Person的派生类Student
class Student : public Person
{
protected:int _num; //学号
};
int main()
{Student s;return 0;
}

结果如下:
在这里插入图片描述

在这里插入图片描述

我们发现对于父类中的成员它会自动调用父类Person的默认构造函数与析构函数

  • 如果父类Person没有默认构造函数,那么我们就需要在初始化列表里显示调用父类的构造函数
    例如:
    在这里插入图片描述

当我们将基类的默认构造函数中的缺省值"tutu",去掉,它就不再是默认构造函数,那么在创建子类Student对象时就不会自动调用默认构造函数,会保错,那么这时我们就需要在初始化列表里显示调用

代码如下:

class Student : public Person
{
public:Student(const char* name, int num):Person(name)	//显示调用父类构造函数, _num(num){}	
protected:int _num; //学号
};int main()
{Student s("tutu", 111);;return 0;
}

结果如下:
在这里插入图片描述

还有一种显示调用情况:

在这里插入图片描述

这种情况是不可取的,这是因为规定在初始化列表中是不可以使用父类的成员的

✨拷贝构造

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

默认生成拷贝构造一般情况下够用,只有当子类成员涉及深拷贝时就必须自己实现拷贝构造

这里也可以自己显示实现一下拷贝构造:

class Student : public Person
{
public://构造函数Student(const char* name, int num):Person(name)	//显示调用父类构造函数, _num(num){}	//拷贝构造Student(const Student& st):Person(st)	//利用前面学习的基类与派生类的赋值转换,_num(st._num){}
protected:int _num; //学号
};

注意这里Person(st)中调用Person中的拷贝构造实现赋值兼容

✨赋值运算符重载

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

✨析构函数

  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

如果自己显示写析构函数:

//析构函数
~Student()
{~Person();//这样写是错误的
}

因为多态的原因,析构函数的名字会被统一处理为destructor(),所以这里调用会构成隐藏,会循环调用,所以要指定作用域:

//析构函数
~Student()
{Person::~Person();cout << "~Student()" << endl;
}

但是我们发现Person的析构函数居然调用了两次:

在这里插入图片描述

这是因为析构函数具有特殊性,在子类析构函数调用完之后会自动调用父类的析构函数,所以即便是自己显示实现了子类的析构函数也不需要自己主动调用父类的析构函数

所以不需要自己主动调用父类的析构函数,否则会报错

其核心原因在于初始化时先构造父类再构造子类,而析构时先析构子类再析构父类,因为子类析构时是可能用到父类成员的,先父后子可能会出错

所以为了保证先析构子类再析构父类,编译器会在析构了子类后自动调用父类的析构函数

总结如下:

默认成员函数\子类成员内置成员自定义成员子类中的父类成员(整体)
默认生成的构造不做处理调用自定义类型的默认构造调用父类的默认构造
默认生成的拷贝构造值拷贝调用自定义类型的拷贝构造调用父类的拷贝构造
默认生成的赋值重载直接赋值调用自定义类型的赋值重载调用父类的赋值重载
默认生成的析构函数不做处理调用自定义类型的析构函数自动调用父类的析构函数

对于构造和析构:
派生类对象初始化先调用基类构造再调派生类构造。
派生类对象析构清理先调用派生类析构再调基类的析构

6.结语

继承可以分为公有继承(public inheritance)、保护继承(protected inheritance)和私有继承(private inheritance)。继承在C++中的应用非常广泛,可以用于构建复杂的类层次结构,提供代码的复用性和灵活性。但是,在使用继承时也需要注意避免多层次的继承导致的类关系复杂性增加,以及合理设计基类和派生类之间的关系。以上就是今天的所以内容啦~ 完结撒花~ 🥳🎉🎉

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

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

相关文章

43.三倍游戏

上海市计算机学会竞赛平台 | YACSYACS 是由上海市计算机学会于2019年发起的活动,旨在激发青少年对学习人工智能与算法设计的热情与兴趣,提升青少年科学素养,引导青少年投身创新发现和科研实践活动。https://www.iai.sh.cn/problem/390 题目描述 三倍游戏是一种单人游戏。玩…

忍法:声音克隆之术

前言&#xff1a; 最近因为一直在给肚子里面的宝宝做故事胎教&#xff0c;每天&#xff08;其实是看自己心情抽空讲下故事&#xff09;都要给宝宝讲故事&#xff0c;心想反正宝宝也看不见我&#xff0c;只听我的声音&#xff0c;干脆偷个懒&#xff0c;克隆自己的声音&#xf…

【SpringBoot】SpringBoot应用Yaml 解析冒号(:)字符串踩坑排查记录

文章目录 一、现象二、原因排查三、怎么办 一、现象 某次在配置中心Nacos上配置了一个字符串&#xff0c;采用YAML格式&#xff0c;如下&#xff1a; id: 114:1代码中采用的是Value注解的形式获取配置&#xff1a; Value("${id}")代码中预期获取的是字符串&#x…

微服务中的Docker详细学习

Docker的个人理解 首先我对于Docker的理解分为两部分&#xff0c;第一是对名字上的理解&#xff0c;我们都知道docker的英文翻译是“码头工人”的意思&#xff0c;所以我们也可以理解为docker是码头上的一个个集装箱的使用。这也与他的图标很相似。其次我是对于其功能上的理解&…

ASP.Net.WebAPI和工具PostMan

1.WebAPI概述 1.1 WebAPI WebAPI 是一种传统的方式&#xff0c;用于构建和暴露 RESTUI风格的Web服务。它提供了丰富的功能和灵活性&#xff0c;可以处理各种HTTP请求&#xff0c;并支持各种数据格式&#xff0c;如JSON、XML等。 WebAPI使用控制器(Controllers)和动作方法(Ac…

力扣每日一题 6/30 记忆化搜索/动态规划

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 494.目标和【中等】 题目&#xff1a; 给你一个非负整数数组 nums 和一个…

INS-GPS组合导航——卡尔曼滤波

系列文章目录 《SAR笔记-卫星轨道建模》 《SAR笔记-卫星轨迹&#xff08;三维建模&#xff09;》 《常用坐标系》 文章目录 前言 一、经典卡尔曼滤波 二、扩展卡尔曼滤波 三、无迹卡尔曼滤波 总结 前言 SAR成像仪器搭载于运动平台&#xff0c;平台的自定位误差将影响SAR…

Redis缓存问题二、缓存雪崩

缓存雪崩 缓存雪崩&#xff1a;是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 缓存雪崩的解决方案&#xff1a; 给不同的Key的TTL添加随机值利用Redis集群提高服务的可用性给缓存业务添加降级限流策略…

数据库物理结构设计-定义数据库模式结构(概念模式、用户外模式、内模式)、定义数据库、物理结构设计策略

一、引言 如何基于具体的DBMS产品&#xff0c;为数据库逻辑结构设计的结果&#xff0c;即关系数据库模式&#xff0c;制定适合应用要求的物理结构 1、在设计数据库物理结构前&#xff0c;数据库设计人员首先 要充分了解所用的DBMS产品的功能、性能和特点&#xff0c;包括提供…

DarkGPT:基于GPT-4-200k设计的人工智能OSINT助手

关于DarkGPT DarkGPT是一款功能强大的人工智能安全助手&#xff0c;该工具基于GPT-4-200k设计并实现其功能&#xff0c;可以帮助广大研究人员针对泄露数据库进行安全分析和数据查询相关的OSINT操作。 工具要求 openai1.13.3 requests python-dotenv pydantic1.10.12 工具安装 …

word2016中新建页面显示出来的页面没有页眉页脚,只显示正文部分。解决办法

问题描述&#xff1a;word2016中新建页面显示出来的页面没有页眉页脚&#xff0c;只显示正文部分。设置了页边距也不管用。 如图1 图1 解决&#xff1a; 点击“视图”——“多页”——“单页”&#xff0c;即可。如图2操作 图2 结果展示&#xff1a;如图3 图3

操作系统精选题(三)(简答题、概念题)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;操作系统 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 简答题 一、对 CPU、内存、外设并…

红黑树插入删除流程(流程图)

红黑树插入删除流程&#xff08;流程图&#xff09; 红黑树性质 左根右(二叉树&#xff09;根叶黑&#xff08;根节点是黑色的&#xff09;不红红&#xff08;不存在相邻两个红色节点&#xff09;黑路同&#xff08;对于每个节点&#xff0c;从该节点出发到任一空叶节点所经过…

==和equals的区别(面试题)

和equals有什么区别 对于基本数据类型&#xff0c;比较的是值是否相等&#xff0c;对于引用类型则是比较的地址是否相等&#xff1b;对于equals来说&#xff0c;基本数据类型没有equals方法&#xff0c;对于引用类型equals比较的是引用对象是否相同 那针对以上结论&#xff0c…

基于模糊神经网络的时间序列预测(以hopkinsirandeath数据集为例,MATLAB)

模糊神经网络从提出发展到今天,主要有三种形式&#xff1a;算术神经网络、逻辑模糊神经网络和混合模糊神经网络。算术神经网络是最基本的&#xff0c;它主要是对输入量进行模糊化&#xff0c;且网络结构中的权重也是模糊权重&#xff1b;逻辑模糊神经网络的主要特点是模糊权值可…

Web后端开发概述环境搭建项目创建servlet生命周期

Web开发概述 web开发指的就是网页向后再让发送请求,与后端程序进行交互 web后端(javaEE)程序需要运行在服务器中 这样前端才可以对其进行进行访问 什么是服务器? 解释1: 服务器就是一款软件,可以向其发送请求,服务器会做出一个响应.可以在服务器中部署文件&#xff0c;让…

pytest-作用域

固件的作用是为了抽离出重复的工作和方便复用&#xff0c;为了更精细化控制固件&#xff08;比如只想对数据库访问测试脚本使用自动连接关闭的固件&#xff09;&#xff0c;pytest 使用作用域来进行指定固件的使用范围。 在定义固件时&#xff0c;通过 scope 参数声明作用域&a…

专业指南:U盘数据恢复全攻略

一、引言&#xff1a;U盘数据恢复的重要性 在信息化日益发展的今天&#xff0c;U盘已成为我们日常生活中不可或缺的存储设备。然而&#xff0c;由于各种原因&#xff0c;U盘中的数据可能会面临丢失的风险。U盘数据恢复技术便应运而生&#xff0c;它旨在帮助用户找回因误删除、…

Unity制作一个简单抽卡系统(简单好抄)

业务流程&#xff1a;点击抽卡——>播放动画——>显示抽卡面板——>将随机结果添加到面板中——>关闭面板 1.准备素材并导入Unity中&#xff08;包含2个抽卡动画&#xff0c;抽卡结果的图片&#xff0c;一个背景图片&#xff0c;一个你的展示图片&#xff09; 2.给…

【前端】简易化看板

【前端】简易化看板 项目简介 看板分为三个模块&#xff0c;分别是待办&#xff0c;正在做&#xff0c;已做完三个部分。每个事件采取"卡片"式设计&#xff0c;支持任务间拖拽&#xff0c;删除等操作。 代码 import React, { useState } from react; import { Car…