【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.mzph.cn/web/37285.shtml

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

相关文章

代码托管服务:GitHub、GitLab、Gitee

目录 引言GitHub&#xff1a;全球最大的代码托管平台概述功能特点适用场景 GitLab&#xff1a;一体化的开发平台概述功能特点适用场景 Gitee&#xff08;码云&#xff09;&#xff1a;中国本土化的代码托管服务概述功能特点适用场景 功能对比结论 引言 在现代软件开发中&#…

C++操作系列(一):MinGW环境安装与配置(无报错版)

本文选择MinGW作为安装对象。 1. 下载MinGW 进入官网&#xff1a;MinGW - Minimalist GNU for Windows download | SourceForge.net 点击File&#xff1a; 划到最下面&#xff1a; &#xfeff; Windows 64位系统下载seh结尾的安装包&#xff1a; 2. 安装MinGW 解压MinGW&am…

扛鼎中国AI搜索,天工凭什么?

人类的创作不会没有瓶颈&#xff0c;但AI的热度可不会消停。 大模型之战依旧精彩&#xff0c;OpenAI选择在Google前一天举行发布会&#xff0c;两家AI企业之间的拉扯赚足了热度。 反观国内&#xff0c;百模大战激发了大家对于科技变革的热切期盼&#xff0c;而如今行业已逐渐…

【操作系统期末速成】 EP01 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️1.1 考点一&#xff1a;操作系统的概率及特征 三、总结&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 回报不在行动…

文章浮现之单细胞VDJ的柱状图

应各位老师的需求复现一篇文章的中的某个图 具体复现图5的整个思路图&#xff0c;这里没有原始数据&#xff0c;所以我使用虚拟生产的metadata进行画图 不废话直接上代码&#xff0c;先上python的代码的结果图 import matplotlib.pyplot as plt import numpy as np# 数据&#…

架构师篇-8、运用事件风暴进行业务领域建

如何成为优秀架构师&#xff1f; 需要有一定的技术积累&#xff0c;但是核心是懂业务。 具备一定的方法&#xff0c;并且有很强的业务理解能力。 技术架构师&#xff1a;形成技术方案&#xff0c;做的更多的是底层的平台&#xff0c;提供工具。 业务架构师&#xff1a;解决方…

两数之和你会,三数之和你也会吗?o_O

前言 多少人梦想开始的地方&#xff0c;两数之和。 但是今天要聊的不是入门第一题&#xff0c;也没有面试官会考这一题吧…不会真有吧&#xff1f; 咳咳不管有没有&#xff0c;今天的猪脚是它的兄弟&#xff0c;三数之和&#xff0c;作为双指针经典题目之一&#xff0c;也是常…

Tomcat的安装和虚拟主机和context配置

一、 安装Tomcat 注意&#xff1a;安装 tomcat 前必须先部署JDK 1. 安装JDK 方法1&#xff1a;Oracle JDK 的二进制文件安装 [rootnode5 ~]# mkdir /data [rootnode5 ~]# cd /data/ [rootnode5 data]# rz[rootnode5 data]# ls jdk-8u291-linux-x64.tar.gz [rootnode5 data]…

【C++】string基本用法(常用接口介绍)

文章目录 一、string介绍二、string类对象的创建&#xff08;常见构造&#xff09;三、string类对象的容量操作1.size()和length()2.capacity()3.empty()4.clear()5.reserve()6.resize() 四、string类对象的遍历与访问1.operator[ ]2.正向迭代器begin()和end()3.反向迭代器rbeg…

QTableView与QSqlQueryModel的简单使用

测试&#xff1a; 这里有一个sqlite数据库 存储了10万多条数据&#xff0c;col1是1,col2是2. 使用QSqlQueryModel和QTableView来显示这些数据&#xff0c;也非常非常流畅。 QString aFile QString::fromLocal8Bit("E:/桌面/3.db");if (aFile.isEmpty())return;//打…

计算机图形学笔记----矩阵

矩阵和标量的运算 ,则 矩阵与矩阵相乘 的矩阵A&#xff0c;的矩阵B。两矩阵&#xff0c;结果为的矩阵&#xff0c;第一个矩阵的列数必须和第二个矩阵的行数相同&#xff0c;否则不能相乘 &#xff0c;中的每个元素等于A的第i行所对应的矢量和B的第j列所对应的矢量进行矢量点…

云计算【第一阶段(22)】Linux的进程和计划任务管理

目录 一、查看进程 1.1、程序和进程的关系 1.2、查看进程 1.2.1、静态查看进程信息ps ​编辑 1.2.1.1、实验 1.2.2、动态查看进程信息top 1.2.2.1、实验 1.2.2.2、top 命令全屏操作界面快捷键 1.2.3、pgrep根据特定条件查询进程pid信息 1.2.4、pstree命令以树形结构列出…

Avue框架学习

Avue框架学习 我们的项目使用的框架是 Avue 在我看来这个框架最大的特点是可以基于JSON配置页面上的From,Table以及各种各样的输入框等,不需要懂前端就可以很快上手,前提是需要多查一下文档 开发环境搭建 由于我本地的环境全是用docker来搭建的,所以我依然选择用docker搭建我…

万字浅析视频搜索系统中的多模态能力建设

万字浅析视频搜索系统中的多模态能力建设 FesianXu 20240331 at Tencent WeChat search team 前言 视频搜索是天然的富媒体检索场景&#xff0c;视觉信息占据了视频的一大部分信息量&#xff0c;在视频搜索系统中引入多模态能力&#xff0c;对于提高整个系统的能力天花板至关重…

机器人控制系列教程之任务空间运动控制器搭建(1)

任务空间运动控制简介 任务空间运动控制—位置被指定给控制器作为末端执行器的姿态。然后&#xff0c;控制器驱动机器人的关节配置到使末端执行器移动到指定姿态的值。这有时被称为操作空间控制。 任务空间运动模型表示机器人在闭环任务空间位置控制下的运动&#xff0c;可使用…

汽车电子工程师入门系列——AUTOSAR通信服务框架(下)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

python格式文件

python小白考后复习 CSV格式文件ini格式文件我们可以读取所有节点还可以输出一个节点下所有键值对组成的元组获取节点下的键对应的值判断节点是否存在添加节点还可以添加键值还可以删除节点 XML格式文件读取若是文件格式存在的xml若是以字符串形式存在的xml获取子标签还有获取子…

【分布式计算框架 MapReduce】高级编程—搜索日志数据分析

目录 一、对于 sogou_500w_utf 数据&#xff0c;使用 MapReduce 编程模型完成对以下数据的分析任务 1. 统计 2011-12-30 日搜索记录&#xff0c;每个时间段的搜索次数 &#xff08;1&#xff09;运行截图 &#xff08;2&#xff09; 源代码 2. 统计 2011-12-30 日 3 点至 …

2024最新初级会计职称题库来啦!!!

16.根据增值税法律制度的规定&#xff0c;下列各项中&#xff0c;属于"提供加工、修理修配劳务"的是&#xff08;&#xff09;。 A.修理小汽车 B.修缮办公楼 C.爆破 D.矿山穿孔 答案&#xff1a;A 解析&#xff1a;选项AB&#xff1a;修理有形动产&#xff08;…

【PL理论深化】(13) 变量与环境:文法结构 | 真假表达式:isZero E | let 表达式叠放 | 定义的规则 | 条件语句的使用

&#x1f4ac; 写在前面&#xff1a;从现在开始&#xff0c;让我们正式设计和实现编程语言。首先&#xff0c;让我们扩展在之前定义的整数表达式语言&#xff0c;以便可以使用变量和条件表达式。 目录 0x00 文法结构 0x01 真假表达式&#xff1a;isZero E 0x02 let 表达式叠…