C++知识整理day9——继承(基类与派生类之间的转换、派生类的默认成员函数、多继承问题)
文章目录
- 1.继承的概念和定义
- 2.基类与派生类之间的转换
- 3.继承中的作用域
- 4.派生类的默认成员函数
- 5.实现一个不能被继承的类
- 6.继承与友元
- 7.继承与静态成员
- 8.多继承和菱形继承问题
- 8.1 继承分类及菱形继承
- 8.2 虚继承
1.继承的概念和定义
概念: 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类或子类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤,继承是类设计层次的复⽤。
下⾯我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/电话/年龄等成员变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。
class Student
{
public:void identify(){//...}void study(){//...}
protected:string _name = "sun";//姓名string _address;//地址string _tel;//电话int _age = 24;//年龄int _id;//学号
};class Teacher
{
public:void identify(){//...}void teaching(){//...}
protected:string _name = "zhangsan";//姓名string _address;//地址string _tel;//电话int _age = 24;//年龄int _title;//职称
};
我们可以看到,学生类和老师类是有很多重复项的;下⾯我们公共的成员都放到Person类中,Student和teacher都继承Person,就可以复⽤这些成员,就不需要重复定义了,省去了很多⿇烦。
class Person
{
public:void identify(){//...}
protected:string _name = "sun";//姓名string _address;//地址string _tel;//电话int _age = 24;//年龄;
};class Student : public Person
{
public:void study(){//...}
protected:int _id;
};class Teacher : public Person
{
public:void teaching(){//...}
protected:int _title;
};
==定义:==下⾯我们看到Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。(因为翻译的原因,所以既叫基类/派⽣类,也叫⽗类/⼦类)
继承方式有public继承、protected继承和private继承三种。
访问限定符也有public访问、protected访问和private访问三种。
继承基类成员访问方式的变化:
| 类成员/继承方式 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的private成员 | 不可见 | 不可见 | 不可见 |
总结:
- 基类private成员在派⽣类中⽆论以什么⽅式继承都是不可⻅的。这⾥的不可⻅是指基类的私有成员还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问它。
- 基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的。
- 实际上⾯的表格我们进⾏⼀下总结会发现,基类的私有成员在派⽣类都是不可⻅。基类的其他成员在派⽣类的访问⽅式 == Min(成员在基类的访问限定符,继承⽅式),public > protected > private。
- 使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最显示的写出继承⽅式。
- 在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实际中扩展维护性不强。
这里有一个特例:就是当基类是类模板时,需要指定⼀下类域。示例:
namespace sun
{template<class T>class stack : public std::vector<T>{public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,// 否则编译报错:error C3861: “push_back”: 找不到标识符vector<T>::push_back(x);// 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到}};
}
2.基类与派生类之间的转换
- public继承的派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤。这⾥有个形象的说法叫切⽚或者切割。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分。
- 基类对象不能赋值给派⽣类对象。
- 基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type Information)dynamic_cast 来进⾏识别后进⾏安全转换。(ps:这个我们后⾯类型转换章节再单独专⻔讲解,这⾥先提⼀下)

示例:
class Person
{
protected:string _name;string _sex;int _age;
};class Student : public Person
{
private:int _id;
};int main()
{Student s1;//派生类的对象可以直接赋值给基类的指针或者引用Person* pp = &s1;Person& p = s1;return 0;
}
3.继承中的作用域
规则:
- 在继承体系中基类和派⽣类都有独⽴的作⽤域。
- 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
示例:
//_num构成了隐藏关系
//派生类对象不指定作用域的情况下,会优先使用派生类的_num
class Person
{
protected:string _name;int _num;
};class Student : public Person
{
public:void Print(){}
protected:int _num;//学号
};
阅读如下一段代码,回答以下两道选择题。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" <<i<<endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};
-
A和B类中的两个func构成什么关系(B)
A. 重载 B. 隐藏 C.没关系 -
下⾯程序的编译运⾏结果是什么(A)
A. 编译报错 B. 运⾏报错 C. 正常运⾏
4.派生类的默认成员函数
6个默认成员函数,默认的意思就是指我们不写,编译器会变我们⾃动⽣成⼀个,那么在派⽣类中,这⼏个成员函数是如何⽣成的呢?
- 派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造函数,则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤。
- 派⽣类的拷贝构造函数必须调⽤基类的拷贝构造完成基类的拷贝初始化。
- 派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域。
- 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。
- 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。
- 派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。
- 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。

示例:我们来写一个案例
#include <iostream>
#include <string>using namespace std;class Person
{
public:Person(const char* name = "sun"):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Perosn(const Person& p)" << endl;}Person& operator==(const Person& p){cout << "operator==(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name;//名字
};class Student : public Person
{
public:Student(const char* name, int id):Person(name),_id(id){cout << "Stuent(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(){cout << "~Student()" << endl;}
protected:int _id;//学号
};int main()
{Student s1("sun", 24);Student s2(s1);Student s3("tong", 18);s1 = s3;return 0;
}
下面我们来看一下详细的运行结果:

5.实现一个不能被继承的类
- 方法一:把基类的构造函数设置成私有,因为派生类继承了基类对象,派生类在调用自己的构造函数时,必须要调用基类的构造函数。但是基类的构造函数成了私有之后,派生类对象就无法调用,那也就没法实例化派生类对象了 。(C98方法)
- 方法二:C++11提供了一个final关键字,final修改基类,就无法被其他类继承了。(推荐使用方法二)注意:final关键字写在类名的后面
示例:
class Person final
{
public:Person(const char* name = "sun"):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Perosn(const Person& p)" << endl;}Person& operator==(const Person& p){cout << "operator==(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name;//名字
};
6.继承与友元
结论:友元关系不可以被继承,也就是说基类的友元不可以访问派生类中的私有和保护成员。
示例:

解决方案:把Display也变成Student类的友元。
7.继承与静态成员
结论:基类定义了static静态成员,则整个继承体系里面只能有一个这样的成员。无论派生出多少个派生类,都只有一个static成员。
示例:
class Student;class Person
{
public:string _name;static int _count;//静态成员变量只能类内声明,类外定义!!!
};int Person::_count = 0;class Student : public Person
{
protected:int _id;//学号
};int main()
{Person p;Student s;cout << &p._name << endl;cout << &s._name << endl;cout << &p._count << endl;cout << &s._count << endl;//共有的情况下,父类或派生类可以指定类域访问静态成员cout << Person::_count << endl;return 0;
}

8.多继承和菱形继承问题
8.1 继承分类及菱形继承
- 单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
- 多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型时,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
- 菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有 数据冗余和⼆义性 的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。

示例:
class Person
{
public:string _name;
};class Student : public Person
{
protected:int _id;//学号
};class Teacher : public Person
{
protected:int _num;//编号
};class Assistant : public Student, public Teacher
{
protected:string _course;//课程
};int main()
{Assistant a;//a._name = "sun";//编译报错,E0266 "Assistant::_name" 不明确//需要指明哪个基类下的成员-》解决二义性a.Student::_name = "zhangsan";a.Teacher::_name = "lisi";return 0;
}
8.2 虚继承
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。
class Person
{
public:string _name; // 姓名
};
// 使⽤虚继承Person类
class Student : virtual public Person
{
protected:int _num; //学号
};
// 使⽤虚继承Person类
class Teacher : virtual public Person
{
protected:int _id; // 职⼯编号
};
// 教授助理
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
int main()
{// 使⽤虚继承,可以解决数据冗余和⼆义性Assistant a;a._name = "peter";return 0;
}
注意:我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤还是底层
都会复杂很多。当然有多继承语法⽀持,就⼀定存在会设计出菱形继承,像Java是不⽀持多继承的,
就避开了菱形继承。
虚继承的底层很复杂,编译器会为虚继承的类创建一个虚基类表(VBT,Virtual Base Table),其中存储指向虚基类的指针(VBP,Virtual Base Pointer)。我们最好不要使用菱形继承。
下面我们以一道题目收尾!!!
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}
多继承中指针偏移问题?下⾯说法正确的是( C )
A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
【解析】对于p1和p2使我们上面的切片操作,而p3单纯就是派生类的指针,所以p3指向d的初始位置。但是呢

我们看这幅图可以清晰的看出三个指针的指向。
相关文章:
C++知识整理day9——继承(基类与派生类之间的转换、派生类的默认成员函数、多继承问题)
文章目录 1.继承的概念和定义2.基类与派生类之间的转换3.继承中的作用域4.派生类的默认成员函数5.实现一个不能被继承的类6.继承与友元7.继承与静态成员8.多继承和菱形继承问题8.1 继承分类及菱形继承8.2 虚继承 1.继承的概念和定义 概念: 继承(inheritance)机制是⾯…...
pyautogui库的screenshot()函数
# 方法一 screenshot pyautogui.screenshot() screenshot.save("screenshot.png")# 方法二 # 获取屏幕分辨率 screen_width, screen_height pyautogui.size()# 截取桌面屏幕 screenshot pyautogui.screenshot(region(0, 0, screen_width, screen_height)) screens…...
App测试--逍遥模拟器抓包问题
一、环境 逍遥模拟器、burp、adb、openssl(kali)。 二、配置 1.burp证书转换 下载证书 将burp证书复制进kali,使用kali的openssl(自带),执行以下命令。 openssl x509 -inform der -in cacert.der -out burp.pem openssl x509 -subject_hash_old -in…...
STM32 HAL库0.96寸OLED显示液晶屏
本文介绍了使用STM32 HAL库通过I2C协议驱动0.96寸OLED显示屏的方法。首先概述了OLED的基本特性和应用,然后详细讲解了汉字点阵生成的方法,并提供了完整的代码示例,包括初始化、清屏、字符串显示和自定义汉字显示函数。这些代码实现了在STM32F…...
动态表头导出EasyExcel
在 Spring Boot 中结合 EasyExcel 实现动态表头导出(无实体类,表头和字段(前端传表名,字段值动态查询,返回List<Map<String,Object>>)由前端传递)可以通过以下步骤实现。以下是完整…...
【前端】react+ts 轮播图的实现
一、场景描述 在很多网站的页面中都有轮播图,所以我想利用react.js和ts实现一个轮播图。自动轮播图已经在前面实现过了,如:https://blog.csdn.net/weixin_43872912/article/details/145622444?sharetypeblogdetail&sharerId145622444&a…...
清华大学出品DeepSeek 四部教程全收录(附下载包),清华deepseek文档下载地址
文章目录 前言一、清华大学deepseek教程(四部)二、清华大学deepseek教程全集1.清华大学第一版《DeepSeek:从入门到精通》2.清华大学第二版《DeepSeek赋能职场》3.清华大学第三版《普通人如何抓住DeepSeek红利》4.清华大学第四版:D…...
Android 布局系列(三):RelativeLayout 使用指南
引言 在 Android 开发中,布局管理是构建用户界面的核心。RelativeLayout 曾经是 Android 中非常流行的一种布局方式,广泛应用于各种项目中。它通过相对位置关系组织视图元素,使得我们可以根据父容器或者其他视图的位置来灵活调整子视图的布局…...
ubuntu20.04音频aplay调试
1、使用指定声卡,aplay 播放命令 aplay -D plughw:1,0 test2.wav2、 录音 arecord -Dhw:1,0 -d 10 -f cd -r 44100 -c 2 -t wav test.wav3、各个参数含义 -D 指定声卡编号 plughw:0,0 //0,0代表card0,device0,可以通过arecord -l获取 -f 录音格式 S16_LE…...
前缀和代码解析
前缀和是指数组一定范围的数的总和,常见的有两种,一维和二维,我会用两道题来分别解析 一维 DP34 【模板】前缀和 题目: 题目解析: 暴力解法 直接遍历数组,遍历到下标为 l 时,开始进行相加,直到遍历到下标为 r ,最后返回总和.这样做的时间复杂度为: O(n) public class Main …...
Windows 环境下安装 Anaconda 并配置
安装Anaconda 1. 下载安装包 官网下载:https://www.anaconda.com/download/success 也可以从国内镜像仓库下载: 中国科学技术大学 https://mirrors.ustc.edu.cn/ 清华大学开源软件镜像站 https://mirrors.tuna.tsinghua.edu.cn/ 2. 安装过程 双…...
大模型在尿潴留风险预测及围手术期方案制定中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 研究方法与数据来源 二、大模型预测尿潴留的原理与方法 2.1 相关大模型介绍 2.2 模型构建与训练 2.3 模型评估指标与验证 三、术前尿潴留风险预测及方案制定 3.1 术前风险因素分析 3.2 大模型预测结果分析 3.3 …...
JavaScript 简单类型与复杂类型
在JavaScript中,根据数据存储的方式不同,变量可以分为两大类:简单类型(也称为基本数据类型或原始类型)和复杂类型(也称为引用数据类型)。理解这两者的区别对于编写高效且无误的代码至关重要。本…...
AI绘画软件Stable Diffusion详解教程(1):Windows系统本地化部署操作方法(专业版)
一、事前准备 1、一台配置不错的电脑,英伟达显卡,20系列起步,建议显存6G起步,安装win10或以上版本,我的显卡是40系列,16G显存,所以跑大部分的模型都比较快; 2、科学上网࿰…...
大白话Vue 双向数据绑定的实现原理与数据劫持技术
咱们来好好唠唠Vue双向数据绑定的实现原理和数据劫持技术,我会用特别通俗的例子给你讲明白。 啥是双向数据绑定 你可以把双向数据绑定想象成一个神奇的“同步器”。在网页里有两部分,一部分是数据,就像你记在小本本上的信息;另一…...
VUE 获取视频时长,无需修改数据库,前提当前查看视频可以得到时长
第一字段处 <el-table-column label"视频时长" align"center"> <template slot-scope"scope"> <span>{{ formatDuration(scope.row.duration) }}</span> </template> </el-ta…...
antv G6绘制流程图
效果图(优点:可以自定义每一条折线的颜色,可以自定义节点的颜色,以及折线的计算样式等): 代码: <!-- 流程图组件 --> <template><div id"container"></div>…...
完美隐藏滚动条方案 (2024 最新验证)
完美隐藏滚动条方案 (2024 最新验证) css /* 全局隐藏竖直滚动条但保留滚动功能 */ html {overflow: -moz-scrollbars-none; /* Firefox 旧版 */scrollbar-width: none; /* Firefox 64 */-ms-overflow-style: none; /* IE/Edge */overflow-y: overlay; …...
单片机的串口(USART)
Tx - 数据的发送引脚,Rx - 数据的接受引脚。 串口的数据帧格式 空闲状态高电平,起始位低电平,数据位有8位校验位,9位校验位,停止位是高电平保持一位或者半位,又或者两位的状态。 8位无校验位传输一个字节…...
实现分布式限流开源项目
以下是10个可以实现分布式限流中间件的开源项目推荐,这些项目基于不同的技术栈,适用于多种应用场景: 1. **Alibaba Sentinel** Sentinel 是阿里巴巴开源的分布式限流中间件,支持多种限流策略(如QPS、并发线程数等…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...

