C++三大特性之继承,详细介绍
阿尼亚全程陪伴大家学习~
前言
每个程序员在开发新系统时,都希望能够利用已有的软件资源,以缩短开发周期,提高开发效率。 为了提高软件的可重用性(reusability),C++提供了类的继承机制。
1.继承的概念
继承: 指在现有类的基础上建立一个新的类。现有类称为基类或父类,新建类称为派生类或子类。对于父类与子类,人们也常说子类继承了父类,或者父类派生了子类。
语法:class 子类(派生类):继承方式 父类(基类)
现在我们一起来看一下具体的实现
#include<iostream>
using namespace std;
//基础界面
class BasePage
{void left(){cout << "Java,C++,Python,C...." << endl;}void right(){cout << "右界面" << endl;}void head(){cout << "头部界面" << endl;}void bottom(){cout << "底部界面" << endl;}
};
//C语言界面
class C
{
public:void left(){cout << "Java,C++,Python,C...." << endl;}void right(){cout << "右界面" << endl;}void head(){cout << "头部界面" << endl;}void bottom(){cout << "底部界面" << endl;}void Linux(){cout << "Linux" << endl;}
};
//C语言界面
class C :public BasePage
{
public:void Linux(){cout << "Linux" << endl;}
};
//Java界面
class Java :public BasePage
{
public:void JavaSE(){cout << "JavaSE" << endl;}
};
这可以看做一个编程语言学习的主界面,左界面是各种不同语言的分类,这些不同编程语言界面都有主界面的部分,但是他们也有自己独特的部分,主界面是基类,而具体的编程语言界面则是派生类,派生类都继承了父类(基类)所有的成员函数。
2.继承的三种方式
公共(public)继承、保护(protected)继承、私有(private)继承

下面我们一一介绍
2.1公共继承
#include<iostream>
using namespace std;
class A
{
private:int _a = 1;
public:int _A = 10;void A_print(){cout << "_a=" << _a << endl;cout << "_A=" << _A << endl;}
};
class B:public A
{
private:int _b = 2;
public:int _B = 20;void B_print(){cout << "_b=" << _b << endl;}
};
int main()
{B b;b.B_print();b.A_print();return 0;
}

我们发现通过公共继承的方式来继承A类,A类中的打印函数也被继承了,所以B类的对象可以调用A类的成员函数,接下来我们修改一下代码,在B类中访问A类的成员变量


编译器告诉我们_a不可以被访问因为他是A类中的私有成员,而_A可以被访问因为他是A类中的公有成员,那有没有什么方法能让_a也能被访问呢?
有。一种是把_a设置为public成员,另外一种是把_a设置为protected成员(最好的做法),这三种又有什么区别呢?(重要!!!)
*公有(public)成员:
公有成员可以从任何地方被访问,包括类的内部、类的派生类以及类的外部。
把_a设置为公有成员意味着任何地方的代码都可以直接访问它,这通常不是一个好的做法,因为它破坏了封装性(封装性意味着隐藏对象的内部状态以防止它们被外部代码直接访问)。*私有(private)成员:
私有成员只能在类的内部被访问。
编译器告诉我们_a不可以被访问,因为它被声明为私有成员。这意味着你不能从类的外部或派生类中直接访问它。*保护(protected)成员:
保护成员可以在类的内部和派生类中被访问,但不能在类的外部被访问。
把_a设置为保护成员意味着你可以在其派生类中访问它,但不能在类的外部直接访问它。这提供了一种在派生类中重用和扩展基类功能的方式,同时保持对外部世界的封装性。
能明白三者的差异,也就很容易理解三种继承方式的差别了
#include<iostream>
using namespace std;
class A
{
private:int _a = 1;
protected:int a = 100;
public:int _A = 10;void A_print()//A类打印函数{cout << "_a=" << _a << endl;cout << "_A=" << _A << endl;cout << "a=" << a << endl;}
};
class B:public A
{
private:int _b = 2;
public:void B_print()//B类打印函数{cout << "_b=" << _b << endl;//cout << "_a=" << _a << endl;//A类私有成员不能被访问cout << "_A=" << _A << endl;//A类公有成员可以被访问cout << "a=" << a << endl;//A类保护成员可以被访问A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();cout << endl;cout << b._A << endl;//public继承方式,在类的外部能访问类public成员b.A_print();//public继承方式,在类的外部能访问类public成员//cout << b.a << endl;//在类的外部不能访问类protected成员return 0;
}
2.2保护继承
#include<iostream>
using namespace std;
class A
{
private:int _a = 1;
protected:int a = 100;
public:int _A = 10;void A_print()//A类打印函数{cout << "_a=" << _a << endl;cout << "_A=" << _A << endl;cout << "a=" << a << endl;}
};
class B:protected A
{
private:int _b = 2;
public:void B_print()//B类打印函数{cout << "_b=" << _b << endl;//cout << "_a=" << _a << endl;//A类私有成员不能被访问cout << "_A=" << _A << endl;//A类公有成员可以被访问cout << "a=" << a << endl;//A类保护成员可以被访问A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();//b.A_print();//protected继承方式,在类的外部不能访问类成员return 0;
}

2.3私有继承
#include<iostream>
using namespace std;
class A
{
private:int _a = 1;
protected:int a = 100;
public:int _A = 10;void A_print()//A类打印函数{cout << "_a=" << _a << endl;cout << "_A=" << _A << endl;cout << "a=" << a << endl;}
};
class B:private A
{
private:int _b = 2;
public:void B_print()//B类打印函数{cout << "_b=" << _b << endl;//cout << "_a=" << _a << endl;//A类私有成员不能被访问cout << "_A=" << _A << endl;//A类公有成员可以被访问cout << "a=" << a << endl;//A类保护成员可以被访问//A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();cout << endl;//cout << b._A << endl;//private继承方式,在类的外部不能访问类public成员//cout << b.a << endl;private继承方式,在类的外部不能访问类protected成员//b.A_print();//private继承方式,在类的外部不能访问类public成员return 0;
}

总结
在类的内部,派生类无论是以哪种方式继承基类,都不能访问基类的private成员,而基类的public成员、protected成员可以被派生类访问
在类的外部,首先需要明确的是无论是基类还是派生类的private成员和protected成员都是不能直接被访问的。而public继承的方式可以通过派生类的对象访问基类的public成员,而private继承的方式和protected继承的方式中,却不可以通过派生类的对象访问基类的public成员(可以这么理解此时基类的public成员分别成了派生类的私有成员和保护成员)。
下图辅助理解


3.继承的对象模型

我们发现B类的大小是16个字节,但是我们之前继承方式当中不是说基类的私有成员派生类不能访问吗?
其实父类中所有非静态成员都会被子类继承,父类中的私有成员属性其实是被编译器隐藏了,因此访问不到,但是确实被继承了下来,下面我们来验证一下
首先我们先打开这个工具(开发人员命令提示符)

指令输入步骤

最终呈现结果

经过验证是不是更可靠了呢
4.派生类的构造函数和析构函数
4.1构造函数
派生类不继承基类的构造函数,在声明派生类时一般应定义自己的构造函数
注意
派生类构造函数的总参数表中的参数,应当包括调用基类构造函数所需的参数 派生类构造函数的执行过程是,先调用基类构造函数初始化基类成员,然后对新增成员初始化
#include<iostream>
#include<string>
using namespace std;class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public://Person构造函数Person(string name,char sex,int age):_name(name),_sex(sex),_age(age){}void PersonPrint(){cout << "name:" << _name << endl;cout << "sex:" << _sex << endl;cout << "age:" << _age << endl;}
};
//派生类
class Student :public Person
{
private:int _num;//学号
public://Student构造函数Student(string name,char sex,int age,int num):Person(name,sex,age),_num(num){}void StudentPrint(){PersonPrint();cout << "num:" << _num << endl;}
};
int main()
{Student s("liming", 'M', 18, 1001);s.StudentPrint();return 0;
}

4.1.1有子对象的派生类的构造函数
在学习结构体时,我们讲到一个结构体的成员还可以是个结构体变量。 派生类也可以有子对象(类类型的成员变量)

#include<iostream>
#include<string>
using namespace std;class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public://Person构造函数Person(string name,char sex,int age):_name(name),_sex(sex),_age(age){}void PersonPrint(){cout << "name:" << _name << endl;cout << "sex:" << _sex << endl;cout << "age:" << _age << endl;}
};
//派生类
class Student :public Person
{
private:Person _teacher;//班主任(子对象)int _num;//学号
public://Student构造函数Student(string name,char sex,int age,int num,const Person& teacher):Person(name,sex,age),_num(num),_teacher(teacher){}void StudentPrint(){PersonPrint();//打印学生的信息cout << "num:" << _num << endl;_teacher.PersonPrint();//打印老师的信息}
};
int main()
{Person teacher("zhaoli", 'F', 38);//班主任Student s("liming", 'M', 18, 1001, teacher);//学生s.StudentPrint();return 0;
}

基类构造函数和子对象的书写顺序可以任意
这里有子对象的派生类的构造函数还有另外两种写法
写法一
这种不如引用传参更安全,效率高

写法二
这种写法相对比较麻烦,写的形参更多了

4.1.2派生类构造函数的执行顺序
结论:
先调用基类构造函数,对基类数据成员初始化
再调用子对象类的构造函数,对子对象的数据成员初始化
最后执行派生类构造函数体中的语句,对派生类新增数据成员初始化
验证

4.2析构函数
1.派生类不继承基类的析构函数,在声明派生类时,应当定义自己的析构函数
2.派生类的析构函数只对新增成员进行清理工作,基类、子对象的清理工作仍由它们各自的析构函数负责。
3.在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,分别对基类和子对象进行清理
4.析构函数的执行顺序与构造函数正好相反 先执行派生类自己的析构函数,对派生类新增成员进行清理; 然后调用子对象的析构函数,对子对象进行清理; 最后调用基类的析构函数,对基类进行清理。
析构顺序验证

我们发现Person类只构造了两次,居然析构了三次,这是为什么呢?
其实是因为默认的Person类拷贝构造函数,现在我们显示写一下他的拷贝构造函数来验证一下
#include<iostream>
#include<string>
using namespace std;
class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public:void PersonPrint(){cout << "name:" << _name << endl;cout << "sex:" << _sex << endl;cout << "age:" << _age << endl;}//Person构造函数Person(string name,char sex,int age) :_name(name), _sex(sex), _age(age){cout << "Person构造" << endl;}//Person拷贝构造Person(const Person& p){_name = p._name;_sex = p._sex;_age = p._age;cout << "Person拷贝构造" << endl;}~Person(){cout << "~Person析构" << endl;}
};
class Student :public Person
{
private:Person _teacher;//班主任(子对象)int _num;//学号
public://Student构造函数Student(string name, char sex, int age, int num, const Person& teacher):Person(name, sex, age), _num(num), _teacher(teacher){cout << "Student构造" << endl;}void StudentPrint(){PersonPrint();//打印学生的信息cout << "num:" << _num << endl;_teacher.PersonPrint();//打印老师的信息}~Student(){cout << "~Student析构" << endl;}
};
int main()
{Person teacher("zhaoli", 'F', 38);//班主任cout << "*******************************" << endl;Student s("liming", 'M', 18, 1001, teacher);//学生//s.StudentPrint();return 0;
}

那么谁是拷贝构造的呢?
第一个构造的是Person类的班主任对象,第二个则是调用基类的构造,第三个构造的是子对象是拷贝构造
5.同名成员的处理
假如在A类(基类)和B类(派生类)中有同名数据成员m,同名函数print(),那在类外面访问他们的时候会如果我们想调用A类中的函数print()和访问A类数据成员m,可以通过创建一个A类的对象,通过对象来访问。
但是既然A类被B类通过公共继承的方式继承了,那么A类的公有数据成员m和函数print(),也被继承了,但是B类(派生类)中有同名数据成员m,同名函数print()。此时如果我们建一个B类的对象,访问数据成员m和调用函数print(),会产生二义性吗(冲突)
一起来看一下

证明了同名成员也被继承

我们发现B类的对象访问的都是B类中的数据成员m、函数print(),A类的数据成员m、函数print()并没有被访问,也没有产生二义性,其实派生类会隐藏基类的同名成员,那我们怎么样才能通过派生类的对象,访问基类成员和成员函数呢?
其实很简单只需要加类名::即可

总结:
1.子类对象可以直接访问到子类中同名成员
2.子类对象加作用域可以访问到父类同名成员
3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
补充
继承同名静态成员处理方式
问:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
·访问子类同名成员直接访问即可
·访问父类同名成员需要加作用域
6.多继承
6.1语法
C++允许一个类继承多个类
语法:class子类:继承方式 父类1,继承方式 父类2......
多继承可能会引发父类中有同名成员出现,需要加作用域区分

6.2多继承派生类的构造函数

注意:
⑴初始化表中基类构造函数的排列顺序任意;
⑵派生类D的构造函数的执行顺序是先调用基类的构造函数,再执行派生类构造函数的函数体;
⑶调用基类的构造函数的顺序是按照声明派生类时基类出现的顺序。
示例:


7.菱形继承
如图,先声明A类,然后由它派生出B1类、B2类,D类同时继承了B1类、B2类
当菱形继承,两个父类拥有相同数据,需要加以作用域区分



A类中的数据成员a这份数据我们知道只有有一份就可以,菱形继承导致数据有两份,造成了资源浪费,该如何解决呢?
利用虚继承可以解决,在继承方式之前 加上关键字virtual即可,此时的基类称为虚基类

相关文章:
C++三大特性之继承,详细介绍
阿尼亚全程陪伴大家学习~ 前言 每个程序员在开发新系统时,都希望能够利用已有的软件资源,以缩短开发周期,提高开发效率。 为了提高软件的可重用性(reusability),C提供了类的继承机制。 1.继承的概念 继承: 指在现有…...
Python推导式详解
引言 推导式(Comprehensions)是Python中一种简洁且强大的语法结构,可以用来生成列表、字典和集合。推导式使得代码更加简洁、易读,同时也更具Pythonic风格。今天我将将详细介绍列表推导式、字典推导式和集合推导式…...
stm32中如何实现EXTI线 0 ~ 15与对应IO口的配置呢?
STM32的EXTI控制器支持19 个外部中断/ 事件请求。每个中断设有状态位,每个中断/ 事件都有独立的触发和屏蔽设置。 STM32的19个外部中断对应着19路中断线,分别是EXTI_Line0-EXTI_Line18: 线0~15:对应外部 IO口的输入中断。 线16&…...
Python 短文本匹配,短文本语义相似度,基于大模型的短文本匹配,基于LLMs的短文本语义相似度识别,短文本语义扩充和匹配
1.任务描述 之前在做疾病相似度匹配的时候,堪称史诗级难题,虽然最后加上规则以及一些nlp模型,取得了差强人意的效果,但是短文本的语义相似度匹配一直属于比较难以攻克的难题 2.思路 随着近年大模型的飞速发展,就之前…...
提升接口性能方式汇总
1,sql 2,缓存,尤其面向用户,如app数据。可用redis咖啡,二级缓存。 充分利用redis,redis数据类型很多,平时场景中结合实际情况,找一下对应的redis实现方案 比如Zset可以排序&#…...
C++中的常见语法糖汇总
C中的语法糖是指使代码更简洁、可读性更高的语言特性和简化的语法。以下是一些常见的C语法糖: 1. 自动类型推导(auto) 使用 auto 关键字可以让编译器自动推导变量的类型,简化变量的声明。 auto x 10; // 编译器推导 x…...
TensorFlow Playground神经网络演示工具使用方法详解
在现代机器学习领域,神经网络无疑是一个重要的研究方向。然而,对于许多初学者来说,神经网络的概念和实际操作可能显得相当复杂。幸运的是,TensorFlow Playground 提供了一个交互式的在线工具,使得我们可以直观地理解和实验神经网络的基本原理。在这篇博客中,我们将详细介…...
【git】subtree 简单教程
git subtree使用案例 😄生命不息,写作不止 🔥 继续踏上学习之路,学之分享笔记 👊 总有一天我也能像各位大佬一样 🏆 博客首页 怒放吧德德 To记录领地 🌝分享学习心得,欢迎指正&am…...
C语言基础:字符串函数使用与剖析
strtok(分割字符串) char * strtok ( char * str, const char * sep ); sep参数是个字符串,定义了用作分隔符的字符集合 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。strtok函数找…...
搭建Vulnhub靶机网络问题(获取不到IP)
搭建好靶场后,在攻击机运行arp-scan -l无法发现靶机IP。 这时候去看下靶机网络有没有问题。 重新启动客户机,一直按e进入安全模式(要是直接开机了就先按shift进入grub界面,再按e)找到ro,将ro改为rw signie…...
Prompt 提示词强大方法论和框架2
自从ChatGPT Chat Generative Pre-trained Transformer于2022年11月30日发布以来,一个新兴的行业突然兴起, 那就是提示工程Prompt engineering,可谓如日冲天。 从简单的文章扩写到RAG,ChatGPT展现了前所未有的惊人能力。 在上一…...
C语言分支和循环(2)
我的相关博客: C语言的分支与循环(1) 1.switch语句 除了 if 语句外,C语⾔还提供了 switch 语句来实现分⽀结构。 switch 语句是⼀种特殊形式的 的 if...else 结构,⽤于判断条件有多个结果的情况。它把多重 else if…...
14.FreeRTOS 流媒体缓存 Stream Buffer
FreeRTOS 中的 Stream Buffer(流媒体缓存) 在实时操作系统(RTOS)中,处理流媒体数据是一项非常关键的任务。FreeRTOS 提供了一种名为 Stream Buffer(流媒体缓存)的机制,用于高效地管…...
利用ffmpeg把视频分解成图片(每秒x张图)再图片合成视频
1. 视频分解成图片 ffmpeg -i rawVideo.mp4 -r 5 -f image2 img/%04d.png-i rawVideo.mp4 输入文件 -r 5 每秒5帧(1秒5张图) 可不写,默认每秒24帧 -f image2 表示输出的格式图像 可不写,默认图像 img/ 图片放在img文件夹下 %04d.png 图片的命名…...
冯喜运:6.7今日外汇黄金原油走势分析及日内操作策略
【黄金消息面分析】:美国初请失业金人数超预期,市场对美联储9月降息预期升温,全球降息潮起,黄金市场受支撑。北京时间本周四,美国劳工部公布的数据显示,截至6月1日当周初请失业金人数增加至22.9万人&#x…...
[网络基础]——计算机网络(OSI)参考模型 详解
🏡作者主页:点击! 🌐网络通信基础TCP/IP专栏:点击! ⏰️创作时间:2024年6月2日21点59分 🀄️文章质量:93分 目录 🎟️OSI基本概念 🎄分层架构…...
使用 Java 获取图片的 MD5 编码
在许多应用场景中,我们需要验证文件的完整性或唯一性,常用的方法是计算文件的哈希值。MD5(Message Digest Algorithm 5)是一种广泛使用的哈希函数,可以生成一个128位的哈希值(32位的十六进制数字࿰…...
GO——泛型
泛型 对于强类型语言,在编写代码时不事先指定类型,在实例化的时候作为参数指明类型 参考:https://www.liwenzhou.com/posts/Go/generics/ 什么时候使用泛型? 方法中的代码实现与类型T无关参考:https://juejin.cn/p…...
TSP(Python):Qlearning求解旅行商问题TSP(提供Python代码)
一、Qlearning简介 Q-learning是一种强化学习算法,用于解决基于奖励的决策问题。它是一种无模型的学习方法,通过与环境的交互来学习最优策略。Q-learning的核心思想是通过学习一个Q值函数来指导决策,该函数表示在给定状态下采取某个动作所获…...
【精通NIO】NIO介绍
一、什么是NIO NIO,全称为New Input/Output,是Java平台中用于替代传统I/O(Blocking I/O)模型的一个功能强大的I/O API。NIO在Java 1.4版本中被引入,其设计目标是提供一种非阻塞的、低延迟的I/O操作方式,以…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

