【C++】继承:万字总结

📝前言:
这篇文章我们来讲讲面向对象三大特性之一——继承

🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础,python入门基础,python刷题专栏,Linux


文章目录

  • 一,面相对象三大特性
  • 二,继承
    • 1 大白话讲继承
    • 2 继承定义格式
      • 2.1 继承方式的作用
      • 2.2 继承类模板
        • 2.2.2 需指定类域
        • 2.2.1 按需实例化
    • 3 基类和派生类间的转换
    • 4 继承中的作用域
      • 4.1 隐藏规则
    • 5 派生类的默认成员函数
    • 6 实现⼀个不能被继承的类
    • 7 友元关系不能继承
    • 8 静态成员的继承
    • 9 多继承
    • 10 继承与组合

一,面相对象三大特性

面相对象编程具有三大特性,分别是封装、继承和多态:

  • 封装
    • 概念:将数据和操作数据的方法绑定在一起,组成一个不可分割的整体,即对象。同时,对外部隐藏对象的内部实现细节,只对外提供有限的访问接口迭代器就是一种封装,底层不一样,但是却能用相似的方法访问
    • 作用:通过封装,可以提高代码的安全性和可维护性。避免外部代码直接访问和修改对象的内部数据,防止数据被意外篡改,同时也使得代码的结构更加清晰,各个模块的职责更加明确。
  • 继承
    • 概念允许创建一个新的类(子类),它基于现有的类(父类)进行扩展,子类可以继承父类的属性和方法,并且可以在子类中添加自己特有的属性和方法,或者重写父类的方法。
    • 作用:继承实现了代码的复用,避免了重复编写相似的代码。同时,它也体现了面向对象编程中的“is - a”关系,即子类是父类的一种特殊类型,有助于建立清晰的类层次结构,便于对问题域进行建模。
  • 多态
    • 概念:指同一个方法或操作在不同的对象上可以有不同的表现形式。也就是说,不同的子类对象在调用相同的方法时,可能会执行不同的代码逻辑,产生不同的结果。
    • 作用:多态提高了代码的灵活性和可扩展性。当需要添加新的功能或修改现有功能时,不需要大量修改客户端代码,只需要在相应的子类中进行修改或扩展即可。它使得代码更加易于维护和升级,同时也增强了代码的可读性和可理解性。

二,继承

1 大白话讲继承

简单来说,子类继承父类就是指:子类可以使用父类的成员,并且也可以自己加自己的成员。我们也把父类称为基类,子类称为派⽣类。
示例(下面这个程序是没有问题的):

class Person
{
public:string name;int age;char sex;
};class Student : public Person
{
public:int st_number;
};int main()
{Student st1;st1.sex = 'b';st1.st_number = 23;return 0;
}

在这里,Person是父类,Student是子类
在这里插入图片描述
通过监视窗口我们可以看到,st1里面继承了父类Person的三个成员变量。

2 继承定义格式

在这里插入图片描述

2.1 继承方式的作用

我们都知道,访问限定符有,privatepublic,和protectedprotected就是专门为继承设置的。

继承方式对应也有:privatepublic,和protected

继承类成员访问方式的变化
在这里插入图片描述

  1. 基类private成员在派⽣类中是不可见的。不可见是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问它。(但是其实还是被继承了过去)
  2. protected成员在类外不能直接访问,在子类中可以被访问。
  3. 基类的其他成员在派⽣类的访问⽅式 == Min(成员在基类的访问限定符,继承方式)public > protected >private
  4. class默认的继承⽅式是privatestruct默认的是public,但是建议显式写出继承方式,且一般用public

如,上述代码中父类改成:

class Person
{
public:string name;private:int age;char sex;
};

这时候子类继承后,类外执行st1.sex = 'b';就会报错,因为sex是父类的私有成员

2.2 继承类模板

继承类模板需要注意的是:在子类中使用父类类模板的方法时,如果参数是不确定的,要指定一下父类的类域(才能实例化)

2.2.2 需指定类域
namespace tr
{template<class T>class stack: vector<T>{public:void push(T x){push_back(x);}};
}int main()
{tr::stack<int> st;st.push(3);return 0;
}

报错:
在这里插入图片描述
原因是:

  1. stack<int>实例化时,也实例化vector<int>了,但是不代表push_back实例化了,因为模板是按需实例化的
  2. 到了push操作,编译器要对其实例化,但是因为编译器不知道 push_backvector<T> 里的成员,从而找不到 push_back 这个标识符

正确写法:

void push(T x)
{vector<T>::push_back(x);
}
2.2.1 按需实例化

示例:

namespace tr
{template<class T>class stack: public vector<T>{public:void push(T x){push_back(x);}void print(){cout << "push_back" << endl;}};
}int main()
{tr::stack<int> st;st.print();return 0;
}

上面代码是能正常运行的,原因是实例化stack的时候,并不会把所有成员都实例化了,后面调用谁,才实例化谁。

3 基类和派生类间的转换

对象传递
当派生类对象传递给基类对象的时候,会进行切片,即:派生类对象中基类部分的数据会被复制到基类对象中,而派生类特有的成员则被 “切掉”(会丢失派生类成员的所有信息)。这时候基类对象不能访问派生类的成员,调用父类的成员时,结果也是父类对象的。
在这里插入图片描述
指针/引用传递
public继承的派生类对象的指针/引用 可以赋值基类的指针 / 基类的引用,叫向上传型,也类似做切片。传递后,基类的指针/引用指向派生类,但是只能调用派生类中的基类成员那一部分。
如:

class Person
{
public:string name;int age;char sex;
};class Student : public Person
{
public:int st_number;
};int main()
{Student st1;// 派生类对象赋值给基类的指针/引用Person* p1 = &st1;Person& p2 = st1;// 派生类对象赋值给基类对象(实际上调用的是拷贝构造)Person p3 = st1;return 0;
}

注意:基类的不能赋值给子类(基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time TypeInformation)的dynamic_cast 来进⾏识别后进⾏安全转换。)

4 继承中的作用域

基类和派生类都有独立的作用域

4.1 隐藏规则

隐藏:派⽣类和基类中有同名成员(即同名变量或者函数,函数只要同名就算),派⽣类成员将屏蔽基类对同名成员的直接访问。(也叫做重定义)
如果要访问被隐藏的父类成员,可以指定域。基类名::成员

示例:

class Person
{
public:void print(){cout << "Person" << endl;}string name;int age = 10;char sex;
};class Student : public Person
{
public:void print(){cout << "Student" << endl;}int age = 18;int st_number;};int main()
{Student st;cout << "st.age: " << st.age << endl; // 父类的被隐藏st.Person::print(); // 指定父类的域return 0;
}

只要函数同名就会隐藏,如下也是隐藏:
在这里插入图片描述

5 派生类的默认成员函数

我们可以派生类中的变量看出三种类型:内置类型,自定义类型,来自父类

**在子类继承父类的时候,父类的成员相当于是最先被声明的,然后才到子类自己的成员。**所以调用构造的时候也是,先父类的,再子类的

如果继承多个父类,则先继承的先声明。

  1. 派⽣类中基类的成员,必须调用基类的构造函数来初始化派生类的构造函数会自动调用基类的默认构造,所以,通常,派生类中的成员又有资源申请(需要深拷贝)的时候,我们才需要自己实现构造,拷贝构造和析构也同理)。如果基类没有默认的构造函数,则必须在派⽣类构造函数的初始化列表阶段显式调⽤(基类的构造函数)

示例(子类构造自动调用基类默认构造完成对基类成员的初始化):

class Person
{
public:Person(int age = 10) // 基类有默认构造:name("小红"),age(age),sex('b'){}void print(){cout << "Person" << endl;}string name;int age;char sex;
};class Student : public Person
{
public:Student(int number) // 子类构造自动调用父类默认构造{st_number = number;}void print(int i){cout << "Student" << endl;}int st_number;};int main()
{Student st(23);return 0;
}

在这里插入图片描述
当父类没有默认构造:

class Person
{
public:Person(int a) // 父类没有默认构造:name("小红"),age(a),sex('b'){}void print(){cout << "Person" << endl;}string name;int age;char sex;
};class Student : public Person
{
public:Student(int number){st_number = number;}void print(int i){cout << "Student" << endl;}int st_number;
};int main()
{Student st(23);return 0;
}

报错:
在这里插入图片描述
正确写法:

Student(int number, int a):Person(a) // 在初始化列表显示调用父类的默认构造
{st_number = number;
}
  1. 派⽣类的拷贝构造函数必须调用基类的拷贝构造完成对基类成员的拷贝初始化(如果这个拷贝构造不是缺省的,即不是默认构造函数,也要放在初始化列表)
  2. 派⽣类的operator=必须要调用基类的operator完成基类的复制。需要注意的是派⽣类的
    operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
  3. 派⽣类的析构函数会在被调⽤完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员(后定义的先清理)的顺序
  4. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。如果要显示调用就要指定域。

示例:

class Person
{
public:Person(int a):name("小红"),age(a){}Person(const Person& p){name = p.name;age = p.age;}Person& operator=(const Person& p){if (this != &p){name = p.name;age = p.age;}return *this;}~Person(){cout << "~Person()" << endl;}string name;int age;
};class Student : public Person
{
public:Student(int number, int a):Person(a) // 在初始化列表显示调用父类的默认构造{cout << "Student(int number, int a)" << endl;st_number = number;}// 拷贝构造错误写法:/*Student(const Student& s){Person(s);st_number = s.st_number;cout << "Student(const Student& s)" << endl;}*/// 正确写法:Student(const Student& s): Person(s){st_number = s.st_number;cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s); // 显式调用父类的=重载来初始化父类的成员st_number = s.st_number;}return *this;}~Student(){cout << "~Student()" << endl;}int st_number;
};int main()
{Student st1(23, 18);Student st2(st1);Student st3(25, 35);st1 = st3;return 0;
}

运行结果:
在这里插入图片描述

6 实现⼀个不能被继承的类

  • ⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不见就不能调用了,那么派生类就⽆法实例化出对象。
  • ⽅法2:C++11新增了⼀个final关键字,final修改基类(表示最终类),派⽣类就不能继承了。

示例:class Person final
在这里插入图片描述

7 友元关系不能继承

也就是说基类友元只对基类起作用,而不能访问派⽣类私有和保护成员。

8 静态成员的继承

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例,用的是用一块内存空间的static成员

9 多继承

  • 单继承:⼀个派⽣类只有⼀个直接基类

  • 多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。
    在这里插入图片描述

虚拟菱形继承
在菱形继承结构中,如果存在多层继承关系,使得一个派生类从两个或多个基类中继承了相同的成员,就会产生数据冗余和二义性问题。

虚拟继承就是为了解决这种问题而引入的,通过在继承关系中使用virtual关键字,使得在最终的派生类中只保留一份共享的基类子对象。

比如,在上述StudentTeacher继承时使用虚拟继承,在可能产生数据冗余和二义性的地方添加virtual,就不会产生两个name成员,在通过Assistant访问成员name时,就只有一个,不会产生二义性问题:

如果不添加:

在这里插入图片描述
添加以后:

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 = "tr";return 0;
}

10 继承与组合

  • public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。
  • 组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
  • 继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤。“白箱”:即相对可见。在继承中,基类的内部细节对派⽣类可见,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼
  • 对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。组合类之间没有很强的依赖关系,耦合度低
  • 优先使⽤对象组合有助于你保持每个类被封装,优先使⽤组合(has - a),⽽不是继承(is - 1)

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

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

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

相关文章

Java 架构设计:从单体架构到微服务的转型之路

Java 架构设计&#xff1a;从单体架构到微服务的转型之路 在现代软件开发中&#xff0c;架构设计的选择对系统的可扩展性、可维护性和性能有着深远的影响。随着业务需求的日益复杂和用户规模的不断增长&#xff0c;传统的单体架构逐渐暴露出其局限性&#xff0c;而微服务架构作…

Django3 - 开启Django Hello World

一、开启Django Hello World 要学习Django首先需要了解Django的操作指令&#xff0c;了解了每个指令的作用&#xff0c;才能在MyDjango项目里编写Hello World网页&#xff0c;然后通过该网页我们可以简单了解Django的开发过程。 1.1 Django的操作指令 无论是创建项目还是创建项…

2025阿里云AI 应用-AI Agent 开发新范式-MCP最佳实践-78页.pptx

2025阿里云AI 应用-AI Agent 开发新范式-MCP最佳实践&#xff0c;包含以下内容&#xff1a; 1、AI 应用架构新范式 2、云原生API网关介绍 3、云原生API网关底座核心优势 4、流量网关最佳实践 5、AI 网关代理 LLM 最佳实践 6、MCP网关最佳实践 7、MSE Nacos MCP Server 注册中心…

Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(一)

Pytorch深度学习框架60天进阶学习计划 - 第41天&#xff1a;生成对抗网络进阶&#xff08;一&#xff09; 今天我们将深入探讨生成对抗网络(GAN)的进阶内容&#xff0c;特别是Wasserstein GAN&#xff08;WGAN&#xff09;的梯度惩罚机制&#xff0c;以及条件生成与无监督生成…

大模型到底是怎么产生的?一文了解大模型诞生全过程

前言 大模型到底是怎么产生的呢? 本文将从最基础的概念开始,逐步深入,用通俗易懂的语言为大家揭开大模型的神秘面纱。 大家好,我是大 F,深耕AI算法十余年,互联网大厂核心技术岗。 知行合一,不写水文,喜欢可关注,分享AI算法干货、技术心得。 【专栏介绍】: 欢迎关注《…

五子棋(测试报告)

文章目录 一、项目介绍二、测试用例三、自动化测试用例的部分展示注册登录游戏大厅游戏匹配 总结 一、项目介绍 本项目是一款基于Spring、SpringMVC、MyBatis、WebSocket的双人实时对战五子棋游戏,游戏操作便捷&#xff0c;功能清晰明了。 二、测试用例 三、自动化测试用例的…

idea开发工具多账号使用拉取代码报错问题

设置git不使用凭证管理 把 use credential helper 取消勾选 然后重新pull代码&#xff0c;并勾选remember 这样就可以使用多账号来连接管理代码了

【OpenCV】【XTerminal】talk程序运用和linux进程之间通信程序编写,opencv图像库编程联系

目录 一、talk程序的运用&Linux进程间通信程序的编写 1.1使用talk程序和其他用户交流 1.2用c语言写一个linux进程之间通信&#xff08;聊天&#xff09;的简单程序 1.服务器端程序socket_server.c编写 2.客户端程序socket_client.c编写 3.程序编译与使用 二、编写一个…

【软考系统架构设计师】信息系统基础知识点

1、 信息的特点&#xff1a;客观性&#xff08;真伪性&#xff09;、动态性、层次性、传递性、滞后性、扩压性、分享性 2、 信息化&#xff1a;是指从工业社会到信息社会的演进与变革 3、 信息系统是由计算机硬件、网络和通信设备、计算机软件、信息资源、信息用户和规章制度…

一种基于学习的多尺度方法及其在非弹性碰撞问题中的应用

A learning-based multiscale method and its application to inelastic impact problems 摘要&#xff1a; 我们在工程应用中观察和利用的材料宏观特性&#xff0c;源于电子、原子、缺陷、域等多尺度物理机制间复杂的相互作用。多尺度建模旨在通过利用固有的层次化结构来理解…

基于PyQt5的Jupyter Notebook转Python工具

一、项目背景与核心价值 在数据科学领域,Jupyter Notebook因其交互特性广受欢迎,但在生产环境中通常需要将其转换为标准Python文件。本文介绍一款基于PyQt5开发的桌面级转换工具,具有以下核心价值: 可视化操作:提供友好的GUI界面,告别命令行操作 批量处理:支持目录递归…

图论之并查集——含例题

目录 介绍 秩是什么 例子——快速入门 例题 使用路径压缩&#xff0c;不使用秩合并 使用路径压缩和秩合并 无向图和有向图 介绍 并查集是一种用于 处理不相交集合的合并与查询问题的数据结构。它主要涉及以下基本概念和操作&#xff1a; 基本概念&#xff1a; 集合&…

【数学建模】(智能优化算法)天牛须算法(Beetle Antennae Search, BAS)详解与Python实现

天牛须算法(Beetle Antennae Search, BAS)详解与Python实现 文章目录 天牛须算法(Beetle Antennae Search, BAS)详解与Python实现1. 引言2. 算法原理2.1 基本思想2.2 数学模型 3. Python实现4.实测效果测试1. Michalewicz函数的最小化测试2. Goldstein-Price函数的约束最小化 5…

【家政平台开发(42)】筑牢家政平台安全防线:安全测试与漏洞修复指南

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…

学习笔记八——内存管理相关

&#x1f4d8; 目录 内存结构基础&#xff1a;栈、堆、数据段Rust 的内存管理机制&#xff08;对比 C/C、Java&#xff09;Drop&#xff1a;Rust 的自动清理机制Deref&#xff1a;为什么 *x 能访问结构体内部值Rc&#xff1a;多个变量“共享一个资源”怎么办&#xff1f;Weak&…

ReliefF 的原理

&#x1f31f; ReliefF 是什么&#xff1f; ReliefF 是一种“基于邻居差异”的特征选择方法&#xff0c;用来评估每个特征对分类任务的贡献大小。 它的核心问题是&#xff1a; “我怎么知道某个特征是不是重要&#xff1f;是不是有能力把不同类别的数据区分开&#xff1f;” 而…

​asm汇编源代码之-汉字点阵字库显示程序源代码下载​

汉字点阵字库显示程序 源代码下载 文本模式下显示16x16点阵汉字库内容的程序(标准16x16字库需要使用CHGHZK转换过后才能使用本程序正常显示) 本程序需要调用file.asm和string.asm中的子程序,所以连接时需要把它们连接进来,如下 C:\> tlink showhzk file string 调用参…

【已更新完毕】2025泰迪杯数据挖掘竞赛B题数学建模思路代码文章教学:基于穿戴装备的身体活动监测

基于穿戴装备的身体活动监测 摘要 本研究基于加速度计采集的活动数据&#xff0c;旨在分析和统计100名志愿者在不同身体活动类别下的时长分布。通过对加速度数据的处理&#xff0c;活动被划分为睡眠、静态活动、低强度、中等强度和高强度五类&#xff0c;进而计算每个志愿者在…

Ubuntu24.04装机安装指南

文章目录 Ubuntu24.04装机安装指南一、分区说明二、基础软件三、使用fcitx5配置中文输入法四、安装搜狗输入法【**不推荐**】1. 安装fcitx2. 安装输入法 五、禁用/home目录下自动生成文件夹六、更新软件源1. 针对**新配置方式**的清华源替换方法2. 针对**老配置方式**的清华源替…

互联网三高-数据库高并发之分库分表ShardingJDBC

1 ShardingJDBC介绍 1.1 常见概念术语 ① 数据节点Node&#xff1a;数据分片的最小单元&#xff0c;由数据源名称和数据表组成 如&#xff1a;ds0.product_order_0 ② 真实表&#xff1a;再分片的数据库中真实存在的物理表 如&#xff1a;product_order_0 ③ 逻辑表&#xff1a…