C++ 中的继承详解(上)
目录
1、继承的概念及定义
1.1、继承的概念
1.2、继承定义
1.2.1、定义格式
1.2.2、继承方式
1.2.3、继承基类成员访问方式的变化
2、基类和派生类对象赋值转换
3、继承中的作用域
4、派生类的默认成员函数
补充:封装的层次(实际上有很多层的,这里只写两层)
第一层:数据和方法放到一起,把想给你访问的定义成公有,不想给你访问的定义成私有和保护。
第二层:把一个类型放到另一个类型里面,通过 typedef 或 成员函数 调整,封装出另一个全新的类型 (例如反向迭代器、栈、队列)。
1、继承的概念及定义
1.1、继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
以前我们接触的复用都是“函数复用”,继承是“类”设计层次的“复用”。
简单说就是:假设有好多个类,它们即有着一些属性相同的成员,又有着一些自己属性独特的成员,此时为了避免代码冗余,便将这些属性相同的成员都抽出来放到一个类里,这个类就称之为父类(基类),而原本的这些类就称之为派生类(子类)。
总结:类的继承就是类成员设计的复用。
举个栗子:
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。
// 这里体现出了Student和Teacher复用了Person的成员。
// 通过监视窗口查看Student和Teacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用。class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};void Test()
{Student s;Teacher t;s.Print();t.Print();
}
1.2、继承定义
1.2.1、定义格式
继承的定义格式:

1.2.2、继承方式
public继承
protected继承
private继承
1.2.3、继承基类成员访问方式的变化
| 类成员 / 继承方式 | public继承 | protected继承 | private继承 |
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去直接访问它(可以间接访问,比如在父类中写个get成员函数去获取私有成员...)。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。-- 没有继承的话,甚至可以没有保护protected
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == min(成员在基类的访问限定符,继承方式),public > protected > private。 --注:这里的 min(成员在基类的访问限定符,继承方式) 就是取两者中的更小者
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
5. 在实际运用中一般使用都是public继承,几乎很少使用protetced / private继承,也不提倡使用protetced / private继承,因为protetced / private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
举个栗子:
实例演示三种继承关系下基类成员的各类型成员访问关系的变化
class Person2
{
public: // 直接给你的钱void Print(){cout << _name << endl;cout << _age << endl;}protected: string _name; // 姓名
private: // 私房钱,你不能直接用,但可以间接用int _age; // 年龄
};// class Student : protected Person
// class Student : private Person
class Student2 : public Person2
{
protected:int _stunum; // 学号
};void Test2()
{Student2 s;s.Print();
}
总结:推荐到继承之后类里就尽量使用公有public或保护protected,除非你有特殊的需求,否则一般不再用私有private了。
2、基类和派生类对象赋值转换
注:有个前提,公有继承才可以进行这些赋值转换的操作
1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
注:可以认为每一个子类都是一个特殊的父类。
注:切割/切片赋值兼容 (赋值兼容简单一点理解就是编译器对此进行了特殊处理) 不产生临时对象。
注:该过程,子类的中的内置类型会直接赋值给父类相应的内置类型,自定义类型则会去调用相关的成员函数(拷贝构造、赋值)。
2.基类对象不能赋值给派生类对象。
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run - Time Type Information)的 dynamic_cast 来进行识别后进行安全转换。
举个栗子:
class Person3
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};
class Student3 : public Person3
{
public:int _No; // 学号
};
void Test3()
{Student3 sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person3 pobj = sobj;Person3 *pp = &sobj;Person3 &rp = sobj; // 这一句代码就证明了 切割/切片赋值兼容 不产生临时对象// 这里的 rp 是子类中继承于父类的那一部分的别名,是可以通过它修改子类的内容的rp._age = 10;cout << sobj._age << endl;// 因为正常的隐式类型转换是会产生临时对象的,你使用&,前面就需要加const,因为临时对象具有常性// ex:const string &s = "hello world";// 不加const会报错// 2.基类对象不能赋值给派生类对象// sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针Person3 *pp1 = &sobj; // 指向子类的指针Student3 *ps1 = (Student3 *)pp1; // 这种情况转换是可以的。ps1->_No = 10;Person3 *pp2 = &pobj; // 指向父类的指针Student3 *ps2 = (Student3 *)pp2; // 这种情况转换虽然可以,但是会存在越界访问的问题ps2->_No = 10; // 虽然这里编译不会报错,但实际上是有着越界访问的问题的
}
3、继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
举个栗子:
Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person4
{
protected:string _name = "小李子"; // 姓名int _num = 666; // 身份证号
};
class Student4 : public Person4
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person4::_num << endl;cout << " 学号:" << _num << endl;}protected:int _num = 999; // 学号
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
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 Test4()
{Student4 s4;s4.Print();B b;b.fun(10);b.A::fun(); // 如果你想在外面显示调被隐藏的 成员函数/变量,就这样写
};
补充:如果派生类的成员和基类的成员构成了隐藏,此时用派生类的对象去调用这个成员,编译器会先去派生类中找,找不到了再去基类中找,都找不到就报错。
4、派生类的默认成员函数
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
- 派生类的构造函数会用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用基类的构造函数。
- 派生类的拷贝构造函数会调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的 operator = 会调用基类的 operator = 完成基类的赋值。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。原因:因为在子类的析构函数中还是能显示访问父类的成员(当父类的成员不都是private的时候),所以父类不能先析构了
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
- 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对子类和父类的析构函数名都进行特殊处理,处理成destructor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数会构成隐藏关系。
举个栗子:
class Person5
{
public:Person5(const char *name = "peter"): _name(name){cout << "Person5()" << endl;}Person5(const Person5 &p): _name(p._name){cout << "Person5(const Person5& p)" << endl;}Person5 &operator=(const Person5 &p){cout << "Person5 operator=(const Person5& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person5(){cout << "~Person5()" << endl;}protected:string _name; // 姓名
};class Student5 : public Person5
{
public:Student5(const char* name, int num) // 如果基类没有合适的默认构造,这里就需要你显示写派生类的构造函数,并在初始化列表的部分去显示调用基类的构造函数: Person5::Person5(name) // 这里的显示调用跟 类与对象(下) 中的自定义成员变量在初始化列表显示写时的形式上有点不一样,要注意了// 这里也可以这样写 Person5::Person5(name) 有点像是在给匿名对象进行初始化, _num(num){cout << "Student5()" << endl;}Student5(const Student5& s): Person5::Person5(s) // 必须显示调,虽然不显示调,也能正常运行,但是得到的结果就不是你想要的了。// 注:这里也可以这样写 Person5(s)// 你不显示调,它这里会去走 Person5 的 构造函数,而不是 拷贝构造函数// 注:这一点对于所有成员变量中有其他自定义成员变量的类(不管是继承有的,还是显示写的)都是一样的, _num(s._num){cout << "Student5(const Student5& s)" << endl;}Student5 &operator=(const Student5 &s){cout << "Student5& operator= (const Student5& s)" << endl;if (this != &s){Person5::operator=(s);_num = s._num;}return *this;}// 因为后续多态的一些需要,子类和父类的析构函数名都会被编译器处理成destructor(),这导致子类的析构也会隐藏父类的析构(这里挺奇怪的,毕竟这两个类的析构函数名字可不一样)~Student5(){// ~Person5(); // 这样写会报错Person5::~Person5(); // 这样写才对cout << "~Student5()" << endl;}protected:int _num; // 学号
};
void Test5()
{Student5 s1("jack", 18);Student5 s2(s1);// cout << s2._name << endl;Student5 s3("rose", 17);s1 = s3;
}
总结:
- 构造时:先调用 基类构造函数() 再调用 派生类构造函数() -- 先父后子
- 析构时:先调用 派生类析构函数() 再调用 基类析构函数() -- 先子后父
相关文章:
C++ 中的继承详解(上)
目录 1、继承的概念及定义 1.1、继承的概念 1.2、继承定义 1.2.1、定义格式 1.2.2、继承方式 1.2.3、继承基类成员访问方式的变化 2、基类和派生类对象赋值转换 3、继承中的作用域 4、派生类的默认成员函数 补充:封装的层次(实际上有很多层的,这…...
halcon三维点云数据处理(二十五)moments_object_model_3d
目录 一、moments_object_model_3d例程二、moments_object_model_3d函数三、效果图 一、moments_object_model_3d例程 这个例子说明了如何使用moments_object_model_3d运算符来将3D数据与x、y、z坐标轴对齐。在实际应用中,通过3D传感器获取的物体模型可能具有一个与…...
Mac M3/M4 本地部署Deepseek并集成vscode
Mac 部署 使用傻瓜集成平台ollama,ollama平台依赖于docker,Mac的M3/M4 因doesn’t have VT-X/AMD-v enabled 所以VB,VM无法使用,导致docker无法启动,需要使用docker的替代品podman, 它完全兼容docker brew install p…...
2024年职高单招或高考计算机类投档线
问题: 这些学校2024年职高单招或高考计算机类投档线分别是多少 回答: 部分学校2024年职高单招或高考计算机类投档线如下: 湖南工业职业技术学院 职高单招:未查询到2024年职高单招计算机类专业明确的录取分数线信息。但在2024年…...
Unity Excel导表工具转Lua文件
思路介绍 借助EPPlus读取Excel文件中的配置数据,根据指定的不同类型的数据配置规则来解析成对应的代码文本,将解析出的字符串内容写入到XXX.lua.txt文件中即可 EPPlus常用API //命名空间 using OfficeOpenXml;//Excel文件路径 var fileExcel new File…...
SpringBoot项目集成MinIO
最近在学习MinIO,所以想让自己的SpringBoot项目集成MinIO,在网上查阅资料,并进行操作的过程中遇到一些问题,所以想把自己遇到的坑和完成步骤记录下来供自己和各位查阅。 一. MinIO的下载安装以及基本使用 1. 下载地址:https://d…...
第30篇 基于ARM A9处理器用C语言实现中断<六>
Q:怎样设计基于ARM A9处理器的C语言程序在数码管上滚动显示字符? A:使用A9 Private Timer中断源,控制字符滚动的速度;按键产生中断可以控制字符暂停/继续滚动。 本实验在DE1-SoC开发板的6个七段数码管*HEX5~HEX0*上…...
Flutter 中的单例模式
传统: class RouterManager {// 单例模式static final RouterManager _instance RouterManager._internal();factory RouterManager() {return _instance;}RouterManager._internal(); }传递参数进行初始化时: class RouterManager {// 私有静态实例&a…...
8.python文件
文章目录 1.**文件**1.1**文件是什么**1.2**文件路径**1.3**文件操作**1.3.1**打开文件**1.3.2**关闭文件**1.3.3**写文件**1.3.4**读文件** 1.4**关于中文的处理**1.5**使用上下文管理器** 大家好,我是晓星航。今天为大家带来的是 python文件 相关的讲解࿰…...
2025vue4.x全栈学习关键技术分析线路图
关键升级点说明: 编译优化 :Vue 4.x采用WASM编译提速300% 智能工具链 :Vite插件市场新增AI代码审查模块 跨平台能力 :Uni-App支持原生ARCore/ARKit调用 安全增强 :默认启用WebAuthn生物认证…...
革新之力:数字科技——重塑未来的超越想象之旅
在21世纪的科技浪潮中,数字科技如同一股不可阻挡的洪流,正以前所未有的速度和广度改变着我们的生活、工作乃至整个社会的结构。它不仅是技术的简单迭代,更是对人类社会认知边界的拓宽,对经济模式、社会治理、文化形态等多方面的深…...
超级详细,知识图谱系统的理论详解+部署过程
知识图谱系统(Knowledge Graph System)是一种用于表示、存储、查询和推理知识的系统。它通过结构化的方式将现实世界中的实体、概念及其相互关系组织成一个图结构,从而帮助机器理解和处理复杂的知识。 知识图谱的核心组成部分 实体(Entities): 实体是知识图谱中的节点,…...
电路笔记 (信号): opa tips 放大器反馈电阻并联电容抑制高频噪声的详细推导(传递函数分析)
1. 高频噪声传递函数分析 (1)电路结构 输入信号通过 R 1 R_1 R1 和 C NI C_{\text{NI}} CNI 的并联组合连接到运放的同相输入端。反馈电阻 R 2 R_2 R2 连接在运放的输出端和反相输入端之间。 Layer 1 - 30p R2 R1 R3R1//R2 IN OUT 反向放大电…...
DeepSeek安装部署笔记(一)
Ollamaopen-WebUI部署 DeepSeek安装部署笔记第一步 Ollama安装1.安装ollama:官网https://ollama.com/下载2.上面安装完成,在cmd命令行: 第二步 给DeepSeek添加OpenWebUI界面(重点)1.安装conda:用它来管理py…...
【JavaEE进阶】Spring MVC(4)-图书管理系统案例
欢迎关注个人主页:逸狼 创造不易,可以点点赞吗 如有错误,欢迎指出~ 图书管理系统 创建书籍类BookInfo import lombok.Data;import java.math.BigDecimal;Data //这个类基本上是和数据库对应起来的 public class BookInfo {private Integer id…...
Ubuntu部署ktransformers
准备工作 一台服务器 CPU:500G GPU:48G(NVIDIA4090) 系统:Ubuntu20.04(github的文档好像用的是22.04) 第一步:下载权重文件 1.下载hfd wget https://hf-mirror.com/hfd/hfd.s…...
助力DeepSeek私有化部署服务:让企业AI落地更简单、更安全
在数字化转型的浪潮中,越来越多的企业选择私有化部署AI技术,以保障数据安全、提升业务效率并实现自主可控。DeepSeek作为行业领先的AI开源技术,其技术可以支持企业私有化部署,企业需要一站式服务私有化部署,涵盖硬件采…...
面试官询问项目前后端人员配比之高分示范回答
面试官询问项目前后端人员配比之高分示范回答 以下是对两个项目前后端人员配置的精准分析,结合 技术复杂度、协作效率、风险控制 三个维度设计回答,突出合理性与团队协作意识: 一、《x能x服》项目(Vue重构) 1. 人员配置与分工 前端:1人(独立开发) 负责旧系统业务逻辑…...
MyBatis中的日志和映射器说明
1.MyBatis中的日志 1.1 什么是日志 在我们编写应用的时候,有一些信息需要及时查看,查看的时候有时需要输出到控制台,有时需要输出到文件。MyBatis也需要日志,一般情况下,使用log4j进行日志管理。 1.2 在MyBatis中…...
深入了解 Pinia:Vue 的下一代状态管理工具 (上篇)
引言 在现代前端开发中,状态管理是构建复杂应用的关键。Vue 生态系统中,Vuex 一直是官方推荐的状态管理工具。然而,随着 Vue 3 的发布,一个新的状态管理工具——Pinia,逐渐崭露头角。Pinia 不仅继承了 Vuex 的优点&am…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
